diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ad849002cca1701eedf6fb93fe2817751269a763..6393fdb910e3466dbd2d8706607781ee2c09ce26 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -72,6 +72,7 @@ aconfig_declarations_group { "android.service.dreams.flags-aconfig-java", "android.service.notification.flags-aconfig-java", "android.service.appprediction.flags-aconfig-java", + "android.service.quickaccesswallet.flags-aconfig-java", "android.service.voice.flags-aconfig-java", "android.speech.flags-aconfig-java", "android.systemserver.flags-aconfig-java", @@ -111,6 +112,7 @@ aconfig_declarations_group { "framework_graphics_flags_java_lib", "hwui_flags_java_lib", "interaction_jank_monitor_flags_lib", + "keystore2_flags_java-framework", "libcore_exported_aconfig_flags_lib", "libcore_readonly_aconfig_flags_lib", "libgui_flags_java_lib", @@ -1774,3 +1776,18 @@ java_aconfig_library { aconfig_declarations: "aconfig_settingslib_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Quick Access Wallet +aconfig_declarations { + name: "android.service.quickaccesswallet.flags-aconfig", + package: "android.service.quickaccesswallet", + exportable: true, + container: "system", + srcs: ["core/java/android/service/quickaccesswallet/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.quickaccesswallet.flags-aconfig-java", + aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 48f0928f24d7a0fe0337bde751036c2eae43c4e9..54cb2684068dff0633def876e19762eb4d123929 100644 --- a/Android.bp +++ b/Android.bp @@ -107,7 +107,6 @@ filegroup { ":android.hardware.radio.data-V3-java-source", ":android.hardware.radio.network-V3-java-source", ":android.hardware.radio.voice-V3-java-source", - ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.thermal-V3-java-source", ":android.hardware.tv.tuner-V3-java-source", @@ -116,7 +115,6 @@ filegroup { ":android.security.legacykeystore-java-source", ":android.security.maintenance-java-source", ":android.security.metrics-java-source", - ":android.system.keystore2-V4-java-source", ":android.hardware.cas-V1-java-source", ":credstore_aidl", ":dumpstate_aidl", @@ -149,7 +147,16 @@ filegroup { ":statslog-framework-java-gen", // FrameworkStatsLog.java ":statslog-hwui-java-gen", // HwuiStatsLog.java ":audio_policy_configuration_V7_0", - ], + ] + select(release_flag("RELEASE_ATTEST_MODULES"), { + true: [ + ":android.hardware.security.keymint-V4-java-source", + ":android.system.keystore2-V5-java-source", + ], + default: [ + ":android.hardware.security.keymint-V3-java-source", + ":android.system.keystore2-V4-java-source", + ], + }), } java_library { @@ -398,6 +405,7 @@ java_defaults { "bouncycastle-repackaged-unbundled", "com.android.sysprop.foldlockbehavior", "com.android.sysprop.view", + "configinfra_framework_flags_java_lib", "framework-internal-utils", "dynamic_instrumentation_manager_aidl-java", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed diff --git a/FF_LEADS_OWNERS b/FF_LEADS_OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..a650c6b7a26fd6d6ebc27b38a5cc8790cb273287 --- /dev/null +++ b/FF_LEADS_OWNERS @@ -0,0 +1,10 @@ +bills@google.com +carmenjackson@google.com +nalini@google.com +nosh@google.com +olilan@google.com +philipcuadra@google.com +rajekumar@google.com +shayba@google.com +timmurray@google.com +zezeozue@google.com diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp index e9357f4a9d3cdde2bf1b556bcff0859b160a236e..1175677bbb04a928717274e2f3ceebe5cb581dd7 100644 --- a/apct-tests/perftests/windowmanager/Android.bp +++ b/apct-tests/perftests/windowmanager/Android.bp @@ -13,7 +13,7 @@ // limitations under the License. package { - default_team: "trendy_team_input_framework", + default_team: "trendy_team_windowing_animations_transitions", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index fb5ef8771c26b19a9ff7d482d57e0a342cd4743c..e9b11f46dddeaa5948978508f526c4de9185aad6 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -25,11 +25,13 @@ import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; +import android.app.job.PendingJobReasonsInfo; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; import android.util.ArrayMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -182,6 +184,16 @@ public class JobSchedulerImpl extends JobScheduler { } } + @Override + @NonNull + public List getPendingJobReasonsHistory(int jobId) { + try { + return mBinder.getPendingJobReasonsHistory(mNamespace, jobId); + } catch (RemoteException e) { + return Collections.EMPTY_LIST; + } + } + @Override public boolean canRunUserInitiatedJobs() { try { diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index 21051b520d8406591e3ef77904f8a147786fcd39..dc7f3d143e4ca672f6418ac72f3c866a9ebc7b62 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl @@ -20,6 +20,7 @@ import android.app.job.IUserVisibleJobObserver; import android.app.job.JobInfo; import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; +import android.app.job.PendingJobReasonsInfo; import android.content.pm.ParceledListSlice; import java.util.Map; @@ -40,6 +41,7 @@ interface IJobScheduler { JobInfo getPendingJob(String namespace, int jobId); int getPendingJobReason(String namespace, int jobId); int[] getPendingJobReasons(String namespace, int jobId); + List getPendingJobReasonsHistory(String namespace, int jobId); boolean canRunUserInitiatedJobs(String packageName); boolean hasRunUserInitiatedJobsPermission(String packageName, int userId); List getStartedJobs(); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index bfdd15e9b0cd8f8acf79e409746acfa1cd6f17be..4fbd55a5d528826b93724b269931bac84331f9a3 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -492,6 +492,34 @@ public abstract class JobScheduler { return new int[] { PENDING_JOB_REASON_UNDEFINED }; } + /** + * For the given {@code jobId}, returns a limited historical view of why the job may have + * been pending execution. The returned list is composed of {@link PendingJobReasonsInfo} + * objects, each of which include a timestamp since epoch along with an array of + * unsatisfied constraints represented by {@link PendingJobReason PendingJobReason constants}. + *

+ * These constants could either be explicitly set constraints on the job or implicit + * constraints imposed by the system due to various reasons. + * The results can be used to debug why a given job may have been pending execution. + *

+ * If the only {@link PendingJobReason} for the timestamp is + * {@link PendingJobReason#PENDING_JOB_REASON_UNDEFINED}, it could mean that + * the job was ready to be executed at that point in time. + *

+ * Note: there is no set interval for the timestamps in the returned list since + * constraint changes occur based on device status and various other factors. + *

+ * Note: the pending job reasons history is not persisted across device reboots. + *

+ * @throws IllegalArgumentException if the {@code jobId} is invalid. + * @see #getPendingJobReasons(int) + */ + @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API) + @NonNull + public List getPendingJobReasonsHistory(int jobId) { + throw new UnsupportedOperationException("Not implemented by " + getClass()); + } + /** * Returns {@code true} if the calling app currently holds the * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl similarity index 76% rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt rename to apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl index e4ccc2c553facec92134a5377c820ef4922a0abe..1a027020e25f435cb0614226663501c96137688c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt +++ b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.aidl @@ -14,10 +14,6 @@ * limitations under the License. */ -package com.android.systemui.keyboard.shortcut.shared.model + package android.app.job; -data class ShortcutInfo( - val label: String, - val categoryType: ShortcutCategoryType, - val subCategoryLabel: String, -) + parcelable PendingJobReasonsInfo; diff --git a/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..3c96bab80794d345631e4c199329d64d8f80c841 --- /dev/null +++ b/apex/jobscheduler/framework/java/android/app/job/PendingJobReasonsInfo.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.job; + +import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A simple wrapper which includes a timestamp (in millis since epoch) + * and an array of {@link JobScheduler.PendingJobReason reasons} at that timestamp + * for why a particular job may be pending. + */ +@FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API) +public final class PendingJobReasonsInfo implements Parcelable { + + @CurrentTimeMillisLong + private final long mTimestampMillis; + + @NonNull + @JobScheduler.PendingJobReason + private final int[] mPendingJobReasons; + + public PendingJobReasonsInfo(long timestampMillis, + @NonNull @JobScheduler.PendingJobReason int[] reasons) { + mTimestampMillis = timestampMillis; + mPendingJobReasons = reasons; + } + + /** + * @return the time (in millis since epoch) associated with the set of pending job reasons. + */ + @CurrentTimeMillisLong + public long getTimestampMillis() { + return mTimestampMillis; + } + + /** + * Returns a set of {@link android.app.job.JobScheduler.PendingJobReason reasons} representing + * why the job may not have executed at the associated timestamp. + *

+ * These reasons could either be explicitly set constraints on the job or implicit + * constraints imposed by the system due to various reasons. + *

+ * Note: if the only {@link android.app.job.JobScheduler.PendingJobReason} present is + * {@link JobScheduler.PendingJobReason#PENDING_JOB_REASON_UNDEFINED}, it could mean + * that the job was ready to be executed at that time. + */ + @NonNull + @JobScheduler.PendingJobReason + public int[] getPendingJobReasons() { + return mPendingJobReasons; + } + + private PendingJobReasonsInfo(Parcel in) { + mTimestampMillis = in.readLong(); + mPendingJobReasons = in.createIntArray(); + } + + @NonNull + public static final Creator CREATOR = + new Creator<>() { + @Override + public PendingJobReasonsInfo createFromParcel(Parcel in) { + return new PendingJobReasonsInfo(in); + } + + @Override + public PendingJobReasonsInfo[] newArray(int size) { + return new PendingJobReasonsInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mTimestampMillis); + dest.writeIntArray(mPendingJobReasons); + } +} diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig index d3068d7d37e8210843e947edf7ba91150668fc40..a6e980726a9a3c29b17f1c5be026e357532b3518 100644 --- a/apex/jobscheduler/service/aconfig/alarm.aconfig +++ b/apex/jobscheduler/service/aconfig/alarm.aconfig @@ -1,16 +1,6 @@ package: "com.android.server.alarm" container: "system" -flag { - name: "use_frozen_state_to_drop_listener_alarms" - namespace: "backstage_power" - description: "Use frozen state callback to drop listener alarms for cached apps" - bug: "324470945" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "start_user_before_scheduled_alarms" namespace: "multiuser" diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 033da2df9bf6d66e2eff936b062aa52910e75273..60ba3b896a28cfb8fa9ee4f2e033f9f03e0fecef 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -282,7 +282,6 @@ public class AlarmManagerService extends SystemService { private final Injector mInjector; int mBroadcastRefCount = 0; - boolean mUseFrozenStateToDropListenerAlarms; MetricsHelper mMetricsHelper; PowerManager.WakeLock mWakeLock; SparseIntArray mAlarmsPerUid = new SparseIntArray(); @@ -1784,40 +1783,37 @@ public class AlarmManagerService extends SystemService { mMetricsHelper = new MetricsHelper(getContext(), mLock); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); - mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms(); mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms() && UserManager.supportsMultipleUsers(); if (mStartUserBeforeScheduledAlarms) { mUserWakeupStore = new UserWakeupStore(); mUserWakeupStore.init(); } - if (mUseFrozenStateToDropListenerAlarms) { - final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> { - final int size = frozenStates.length; - if (uids.length != size) { - Slog.wtf(TAG, "Got different length arrays in frozen state callback!" - + " uids.length: " + uids.length + " frozenStates.length: " + size); - // Cannot process received data in any meaningful way. - return; - } - final IntArray affectedUids = new IntArray(); - for (int i = 0; i < size; i++) { - if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) { - continue; - } - if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, - uids[i])) { - continue; - } - affectedUids.add(uids[i]); + final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> { + final int size = frozenStates.length; + if (uids.length != size) { + Slog.wtf(TAG, "Got different length arrays in frozen state callback!" + + " uids.length: " + uids.length + " frozenStates.length: " + size); + // Cannot process received data in any meaningful way. + return; + } + final IntArray affectedUids = new IntArray(); + for (int i = 0; i < size; i++) { + if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) { + continue; } - if (affectedUids.size() > 0) { - removeExactListenerAlarms(affectedUids.toArray()); + if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, + uids[i])) { + continue; } - }; - final ActivityManager am = getContext().getSystemService(ActivityManager.class); - am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback); - } + affectedUids.add(uids[i]); + } + if (affectedUids.size() > 0) { + removeExactListenerAlarms(affectedUids.toArray()); + } + }; + final ActivityManager am = getContext().getSystemService(ActivityManager.class); + am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback); mListenerDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -2994,13 +2990,10 @@ public class AlarmManagerService extends SystemService { pw.println("Feature Flags:"); pw.increaseIndent(); - pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS, - mUseFrozenStateToDropListenerAlarms); - pw.println(); pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS, Flags.startUserBeforeScheduledAlarms()); - pw.decreaseIndent(); pw.println(); + pw.decreaseIndent(); pw.println(); pw.println("App Standby Parole: " + mAppStandbyParole); @@ -5146,38 +5139,6 @@ public class AlarmManagerService extends SystemService { removeForStoppedLocked(uid); } } - - @Override - public void handleUidCachedChanged(int uid, boolean cached) { - if (mUseFrozenStateToDropListenerAlarms) { - // Use ActivityManager#UidFrozenStateChangedCallback instead. - return; - } - if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) { - return; - } - // Apps can quickly get frozen after being cached, breaking the exactness guarantee on - // listener alarms. So going forward, the contract of exact listener alarms explicitly - // states that they will be removed as soon as the app goes out of lifecycle. We still - // allow a short grace period for quick shuffling of proc-states that may happen - // unexpectedly when switching between different lifecycles and is generally hard for - // apps to avoid. - - final long delay; - synchronized (mLock) { - delay = mConstants.CACHED_LISTENER_REMOVAL_DELAY; - } - final Integer uidObj = uid; - - if (cached && !mHandler.hasEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, - uidObj)) { - mHandler.sendMessageDelayed( - mHandler.obtainMessage(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj), - delay); - } else { - mHandler.removeEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj); - } - } }; private final BroadcastStats getStatsLocked(PendingIntent pi) { 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 f569388ef3c19654959cf9a5ec51a4174ed78e49..1c6e40e25a92ef16158233d3083662711d91b590 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -44,6 +44,7 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.app.job.JobSnapshot; import android.app.job.JobWorkItem; +import android.app.job.PendingJobReasonsInfo; import android.app.job.UserVisibleJobSummary; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; @@ -2140,6 +2141,20 @@ public class JobSchedulerService extends com.android.server.SystemService return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED }; } + @NonNull + private List getPendingJobReasonsHistory( + int uid, String namespace, int jobId) { + synchronized (mLock) { + final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId); + if (job == null) { + // Job doesn't exist. + throw new IllegalArgumentException("Invalid job id"); + } + + return job.getPendingJobReasonsHistory(); + } + } + private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) { synchronized (mLock) { ArraySet jobs = mJobs.getJobsByUid(uid); @@ -5121,6 +5136,19 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @Override + public List getPendingJobReasonsHistory(String namespace, int jobId) + throws RemoteException { + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + return JobSchedulerService.this.getPendingJobReasonsHistory( + uid, validateNamespace(namespace), jobId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override public void cancelAll() throws RemoteException { final int uid = Binder.getCallingUid(); @@ -5857,6 +5885,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API, android.app.job.Flags.getPendingJobReasonsApi()); pw.println(); + pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API, + android.app.job.Flags.getPendingJobReasonsHistoryApi()); + pw.println(); pw.decreaseIndent(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index a4a30245084900205b3d529e8847766daa08989f..f3bc9c747f170a020f6968d6e12cdf03b7941fba 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -442,6 +442,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API: pw.println(android.app.job.Flags.getPendingJobReasonsApi()); break; + case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_HISTORY_API: + pw.println(android.app.job.Flags.getPendingJobReasonsHistoryApi()); + break; default: pw.println("Unknown flag: " + flagName); break; 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 58579eb0db479a528a0d635ba2031de9f3162630..b0784f1c69fdaacde8e3c186ae3b9b32d58004d6 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 @@ -32,6 +32,7 @@ import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobWorkItem; +import android.app.job.PendingJobReasonsInfo; import android.app.job.UserVisibleJobSummary; import android.content.ClipData; import android.content.ComponentName; @@ -39,6 +40,7 @@ import android.net.Network; import android.net.NetworkRequest; import android.net.Uri; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.MediaStore; import android.text.format.DateFormat; @@ -72,6 +74,7 @@ import java.security.MessageDigest; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Random; import java.util.function.Predicate; @@ -515,6 +518,10 @@ public final class JobStatus { private final long[] mConstraintUpdatedTimesElapsed = new long[NUM_CONSTRAINT_CHANGE_HISTORY]; private final int[] mConstraintStatusHistory = new int[NUM_CONSTRAINT_CHANGE_HISTORY]; + private final List mPendingJobReasonsHistory = new ArrayList<>(); + private static final int PENDING_JOB_HISTORY_RETURN_LIMIT = 10; + private static final int PENDING_JOB_HISTORY_TRIM_THRESHOLD = 25; + /** * For use only by ContentObserverController: state it is maintaining about content URIs * being observed. @@ -1992,6 +1999,16 @@ public final class JobStatus { mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; } + final int unsatisfiedConstraints = ~satisfiedConstraints + & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); + populatePendingJobReasonsHistoryMap(isReady, nowElapsed, unsatisfiedConstraints); + final int historySize = mPendingJobReasonsHistory.size(); + if (historySize >= PENDING_JOB_HISTORY_TRIM_THRESHOLD) { + // Ensure trimming doesn't occur too often - max history we currently return is 10 + mPendingJobReasonsHistory.subList(0, historySize - PENDING_JOB_HISTORY_RETURN_LIMIT) + .clear(); + } + return true; } @@ -2066,14 +2083,10 @@ public final class JobStatus { } } - /** - * This will return all potential reasons why the job is pending. - */ @NonNull - public int[] getPendingJobReasons() { + public ArrayList constraintsToPendingJobReasons(int unsatisfiedConstraints) { final ArrayList reasons = new ArrayList<>(); - final int unsatisfiedConstraints = ~satisfiedConstraints - & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); + if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) { // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because // the app is background restricted, or because we're restricting background work @@ -2159,6 +2172,18 @@ public final class JobStatus { } } + return reasons; + } + + /** + * This will return all potential reasons why the job is pending. + */ + @NonNull + public int[] getPendingJobReasons() { + final int unsatisfiedConstraints = ~satisfiedConstraints + & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); + final ArrayList reasons = constraintsToPendingJobReasons(unsatisfiedConstraints); + if (reasons.isEmpty()) { if (getEffectiveStandbyBucket() == NEVER_INDEX) { Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); @@ -2178,6 +2203,55 @@ public final class JobStatus { return reasonsArr; } + private void populatePendingJobReasonsHistoryMap(boolean isReady, + long constraintTimestamp, int unsatisfiedConstraints) { + final long constraintTimestampEpoch = // system_boot_time + constraint_satisfied_time + (System.currentTimeMillis() - SystemClock.elapsedRealtime()) + constraintTimestamp; + + if (isReady) { + // Job is ready to execute. At this point, if the job doesn't execute, it might be + // because of the app itself; if not, note it as undefined (documented in javadoc). + mPendingJobReasonsHistory.addLast( + new PendingJobReasonsInfo( + constraintTimestampEpoch, + new int[] { serviceProcessName != null + ? JobScheduler.PENDING_JOB_REASON_APP + : JobScheduler.PENDING_JOB_REASON_UNDEFINED })); + return; + } + + final ArrayList reasons = constraintsToPendingJobReasons(unsatisfiedConstraints); + if (reasons.isEmpty()) { + // If the job is not waiting on any constraints to be met, note it as undefined. + reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED); + } + + final int[] reasonsArr = new int[reasons.size()]; + for (int i = 0; i < reasonsArr.length; i++) { + reasonsArr[i] = reasons.get(i); + } + mPendingJobReasonsHistory.addLast( + new PendingJobReasonsInfo(constraintTimestampEpoch, reasonsArr)); + } + + /** + * Returns the last {@link #PENDING_JOB_HISTORY_RETURN_LIMIT} constraint changes. + */ + @NonNull + public List getPendingJobReasonsHistory() { + final List returnList = + new ArrayList<>(PENDING_JOB_HISTORY_RETURN_LIMIT); + final int historySize = mPendingJobReasonsHistory.size(); + if (historySize != 0) { + returnList.addAll( + mPendingJobReasonsHistory.subList( + Math.max(0, historySize - PENDING_JOB_HISTORY_RETURN_LIMIT), + historySize)); + } + + return returnList; + } + /** @return whether or not the @param constraint is satisfied */ public boolean isConstraintSatisfied(int constraint) { return (satisfiedConstraints&constraint) != 0; diff --git a/api/Android.bp b/api/Android.bp index cdc5cd120956408026e5bced483e2a93b07b13fc..73262030ee379d63a9111deb0d3740efafca938e 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -387,6 +387,7 @@ stubs_defaults { "--error NoSettingsProvider", "--error UnhiddenSystemApi", "--error UnflaggedApi", + "--error FlaggedApiLiteral", "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*", // Disable CallbackInterface, as Java 8 default interface methods avoid the extensibility // issue interfaces had previously. diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 1ebe0cdfabd70eba75c0b50e37f51d31df587b9e..89351fd47ff828baec545fc5460033429e9843d8 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -61,6 +61,7 @@ stubs_defaults { ":framework-bluetooth-sources", ":framework-connectivity-tiramisu-updatable-sources", ":framework-graphics-srcs", + ":framework-healthfitness-sources", ":framework-mediaprovider-sources", ":framework-nearby-sources", ":framework-nfc-updatable-sources", diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 14e238768f4160e4063236a0ff78fb179fda5d05..b43905b1923923f010916c06e26f238a99bdcb3c 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -451,7 +451,7 @@ public: auto token = SurfaceComposerClient::getPhysicalDisplayToken( event.header.displayId); - auto firstDisplay = mBootAnimation->mDisplays.front(); + auto& firstDisplay = mBootAnimation->mDisplays.front(); if (token != firstDisplay.displayToken) { // ignore hotplug of a secondary display continue; diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index f726361effd6b37ae2b65ab0113318e06998ed30..a88796c381662b0c280b2c802b4761da1b50afe6 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -217,6 +217,9 @@ public class AccessibilityNodeInfoDumper { serializer.attribute("", "selected", Boolean.toString(node.isSelected())); serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen( node, width, height).toShortString()); + serializer.attribute("", "drawing-order", Integer.toString(node.getDrawingOrder())); + serializer.attribute("", "hint", safeCharSeqToString(node.getHintText())); + int count = node.getChildCount(); for (int i = 0; i < count; i++) { AccessibilityNodeInfo child = node.getChild(i); diff --git a/core/api/current.txt b/core/api/current.txt index 2f5bde9d37f274d716bdfd2dbe6a7881646738eb..c83212feab6739d32431708b91e96783d053f6cc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -23,6 +23,7 @@ package android { field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"; field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"; + field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public static final String APPLY_PICTURE_PROFILE = "android.permission.APPLY_PICTURE_PROFILE"; field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; @@ -336,7 +337,7 @@ package android { field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS"; field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS"; - field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES"; + field @FlaggedApi("com.android.settingslib.flags.write_system_preference_permission_enabled") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES"; field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL"; } @@ -1276,6 +1277,7 @@ package android { field public static final int paddingStart = 16843699; // 0x10103b3 field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int paddingVertical = 16844094; // 0x101053e + field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat; field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 field public static final int panelColorForeground = 16842848; // 0x1010060 @@ -2043,6 +2045,12 @@ package android { field public static final int system_error_container_light = 17170554; // 0x106007a field public static final int system_error_dark = 17170595; // 0x10600a3 field public static final int system_error_light = 17170552; // 0x1060078 + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_on_surface_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_on_surface_light; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_primary_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_primary_light; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_surface_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_inverse_surface_light; field public static final int system_neutral1_0 = 17170461; // 0x106001d field public static final int system_neutral1_10 = 17170462; // 0x106001e field public static final int system_neutral1_100 = 17170464; // 0x1060020 @@ -2119,12 +2127,16 @@ package android { field public static final int system_primary_fixed = 17170612; // 0x10600b4 field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5 field public static final int system_primary_light = 17170528; // 0x1060060 + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_scrim_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_scrim_light; field public static final int system_secondary_container_dark = 17170573; // 0x106008d field public static final int system_secondary_container_light = 17170530; // 0x1060062 field public static final int system_secondary_dark = 17170575; // 0x106008f field public static final int system_secondary_fixed = 17170616; // 0x10600b8 field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9 field public static final int system_secondary_light = 17170532; // 0x1060064 + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_shadow_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_shadow_light; field public static final int system_surface_bright_dark = 17170590; // 0x106009e field public static final int system_surface_bright_light = 17170547; // 0x1060073 field public static final int system_surface_container_dark = 17170587; // 0x106009b @@ -2142,6 +2154,8 @@ package android { field public static final int system_surface_dim_light = 17170548; // 0x1060074 field public static final int system_surface_disabled = 17170626; // 0x10600c2 field public static final int system_surface_light = 17170540; // 0x106006c + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_surface_tint_dark; + field @FlaggedApi("android.os.material_colors_10_2024") public static final int system_surface_tint_light; field public static final int system_surface_variant_dark = 17170592; // 0x10600a0 field public static final int system_surface_variant_light = 17170549; // 0x1060075 field public static final int system_tertiary_container_dark = 17170577; // 0x1060091 @@ -6221,6 +6235,7 @@ package android.app { } public class KeyguardManager { + method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addDeviceLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.DeviceLockedStateListener); method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addKeyguardLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.KeyguardLockedStateListener); method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence); method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); @@ -6230,10 +6245,15 @@ package android.app { method public boolean isKeyguardLocked(); method public boolean isKeyguardSecure(); method @Deprecated public android.app.KeyguardManager.KeyguardLock newKeyguardLock(String); + method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeDeviceLockedStateListener(@NonNull android.app.KeyguardManager.DeviceLockedStateListener); method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeKeyguardLockedStateListener(@NonNull android.app.KeyguardManager.KeyguardLockedStateListener); method public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable android.app.KeyguardManager.KeyguardDismissCallback); } + @FlaggedApi("android.app.device_unlock_listener") @java.lang.FunctionalInterface public static interface KeyguardManager.DeviceLockedStateListener { + method public void onDeviceLockedStateChanged(boolean); + } + public abstract static class KeyguardManager.KeyguardDismissCallback { ctor public KeyguardManager.KeyguardDismissCallback(); method public void onDismissCancelled(); @@ -8075,8 +8095,10 @@ package android.app.admin { method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String); method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName); method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@Nullable android.content.ComponentName); + method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimePolicy(); method @Deprecated public boolean getAutoTimeRequired(); method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@Nullable android.content.ComponentName); + method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimeZonePolicy(); method @NonNull public java.util.List getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName); method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName); @@ -8232,8 +8254,10 @@ package android.app.admin { method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle); method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@Nullable android.content.ComponentName, boolean); + method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimePolicy(int); method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@Nullable android.content.ComponentName, boolean); + method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZonePolicy(int); method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean); method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public void setCameraDisabled(@Nullable android.content.ComponentName, boolean); @@ -8352,6 +8376,12 @@ package android.app.admin { field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"; field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION"; field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"; + field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_DISABLED = 1; // 0x1 + field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_ENABLED = 2; // 0x2 + field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; // 0x0 + field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_DISABLED = 1; // 0x1 + field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_ENABLED = 2; // 0x2 + field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0 field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_DISABLED = 1; // 0x1 field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_ENABLED = 2; // 0x2 field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; // 0x0 @@ -9109,6 +9139,46 @@ package android.app.blob { } +package android.app.jank { + + @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats { + ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.FrameOverrunHistogram); + method @NonNull public android.app.jank.FrameOverrunHistogram getFrameOverrunHistogram(); + method public long getJankyFrameCount(); + method public long getTotalFrameCount(); + method public int getUid(); + method @NonNull public String getWidgetCategory(); + method @NonNull public String getWidgetId(); + method @NonNull public String getWidgetState(); + field public static final String ANIMATING = "animating"; + field public static final String ANIMATION = "animation"; + field public static final String DRAGGING = "dragging"; + field public static final String FLINGING = "flinging"; + field public static final String KEYBOARD = "keyboard"; + field public static final String MEDIA = "media"; + field public static final String NAVIGATION = "navigation"; + field public static final String NONE = "none"; + field public static final String OTHER = "other"; + field public static final String PLAYBACK = "playback"; + field public static final String PREDICTIVE_BACK = "predictive_back"; + field public static final String SCROLL = "scroll"; + field public static final String SCROLLING = "scrolling"; + field public static final String SWIPING = "swiping"; + field public static final String TAPPING = "tapping"; + field public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified"; + field public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; + field public static final String ZOOMING = "zooming"; + } + + @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class FrameOverrunHistogram { + ctor public FrameOverrunHistogram(); + method public void addFrameOverrunMillis(int); + method @NonNull public int[] getBucketCounters(); + method @NonNull public int[] getBucketEndpointsMillis(); + } + +} + package android.app.job { public class JobInfo implements android.os.Parcelable { @@ -9262,6 +9332,7 @@ package android.app.job { method @Nullable public abstract android.app.job.JobInfo getPendingJob(int); method public int getPendingJobReason(int); method @FlaggedApi("android.app.job.get_pending_job_reasons_api") @NonNull public int[] getPendingJobReasons(int); + method @FlaggedApi("android.app.job.get_pending_job_reasons_history_api") @NonNull public java.util.List getPendingJobReasonsHistory(int); method @NonNull public java.util.Map> getPendingJobsInAllNamespaces(); method public abstract int schedule(@NonNull android.app.job.JobInfo); field public static final int PENDING_JOB_REASON_APP = 1; // 0x1 @@ -9340,6 +9411,15 @@ package android.app.job { method @NonNull public android.app.job.JobWorkItem.Builder setMinimumNetworkChunkBytes(long); } + @FlaggedApi("android.app.job.get_pending_job_reasons_history_api") public final class PendingJobReasonsInfo implements android.os.Parcelable { + ctor public PendingJobReasonsInfo(long, @NonNull int[]); + method public int describeContents(); + method @NonNull public int[] getPendingJobReasons(); + method public long getTimestampMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + } package android.app.people { @@ -13029,6 +13109,7 @@ package android.content.pm { method public boolean hasParentSessionId(); method public boolean isActive(); method public boolean isApplicationEnabledSettingPersistent(); + method @FlaggedApi("android.content.pm.sdk_dependency_installer") public boolean isAutoInstallDependenciesEnabled(); method public boolean isCommitted(); method public boolean isMultiPackage(); method public boolean isPreApprovalRequested(); @@ -13064,6 +13145,7 @@ package android.content.pm { method public void setApplicationEnabledSettingPersistent(); method @Deprecated public void setAutoRevokePermissionsMode(boolean); method public void setDontKillApp(boolean); + method @FlaggedApi("android.content.pm.sdk_dependency_installer") public void setEnableAutoInstallDependencies(boolean); method public void setInstallLocation(int); method public void setInstallReason(int); method public void setInstallScenario(int); @@ -13505,6 +13587,7 @@ package android.content.pm { field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES"; field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"; + field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff field public static final int SIGNATURE_MATCH = 0; // 0x0 field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1 @@ -13741,6 +13824,7 @@ package android.content.pm { public final class SharedLibraryInfo implements android.os.Parcelable { method public int describeContents(); + method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List getCertDigests(); method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage(); method @NonNull public java.util.List getDependentPackages(); method @IntRange(from=0xffffffff) public long getLongVersion(); @@ -17408,6 +17492,7 @@ package android.graphics { method public void setFloatUniform(@NonNull String, @NonNull float[]); method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); method public void setIntUniform(@NonNull String, int, int, int); @@ -17426,7 +17511,29 @@ package android.graphics { method public void setFloatUniform(@NonNull String, float, float, float, float); method public void setFloatUniform(@NonNull String, @NonNull float[]); method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader); + method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); + method public void setIntUniform(@NonNull String, int); + method public void setIntUniform(@NonNull String, int, int); + method public void setIntUniform(@NonNull String, int, int, int); + method public void setIntUniform(@NonNull String, int, int, int, int); + method public void setIntUniform(@NonNull String, @NonNull int[]); + } + + @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeXfermode extends android.graphics.Xfermode { + ctor public RuntimeXfermode(@NonNull String); + method public void setColorUniform(@NonNull String, @ColorInt int); + method public void setColorUniform(@NonNull String, @ColorLong long); + method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color); + method public void setFloatUniform(@NonNull String, float); + method public void setFloatUniform(@NonNull String, float, float); + method public void setFloatUniform(@NonNull String, float, float, float); + method public void setFloatUniform(@NonNull String, float, float, float, float); + method public void setFloatUniform(@NonNull String, @NonNull float[]); + method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); + method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); method public void setIntUniform(@NonNull String, int, int, int); @@ -18764,6 +18871,19 @@ package android.hardware { field public static final int TRANSFER_UNSPECIFIED = 0; // 0x0 } + @FlaggedApi("android.hardware.flags.luts_api") public final class DisplayLuts { + ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts(); + method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry); + method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry, @NonNull android.hardware.DisplayLuts.Entry); + } + + @FlaggedApi("android.hardware.flags.luts_api") public static class DisplayLuts.Entry { + ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts.Entry(@NonNull float[], int, int); + method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public float[] getBuffer(); + method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension(); + method @FlaggedApi("android.hardware.flags.luts_api") public int getSamplingKey(); + } + public class GeomagneticField { ctor public GeomagneticField(float, float, float, long); method public float getDeclination(); @@ -18825,8 +18945,19 @@ package android.hardware { field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c } + @FlaggedApi("android.hardware.flags.luts_api") public final class LutProperties { + method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension(); + method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public int[] getSamplingKeys(); + method @FlaggedApi("android.hardware.flags.luts_api") public int getSize(); + field @FlaggedApi("android.hardware.flags.luts_api") public static final int ONE_DIMENSION = 1; // 0x1 + field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_MAX_RGB = 1; // 0x1 + field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_RGB = 0; // 0x0 + field @FlaggedApi("android.hardware.flags.luts_api") public static final int THREE_DIMENSION = 3; // 0x3 + } + @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable { method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents(); + method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.hardware.LutProperties[] getLutProperties(); method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isCombinationSupported(int, int); method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isMixedColorSpacesSupported(); method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int); @@ -19373,6 +19504,7 @@ package android.hardware.camera2 { field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_AVAILABLE_ANTIBANDING_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_AVAILABLE_MODES; + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_AVAILABLE_PRIORITY_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key> CONTROL_AE_COMPENSATION_RANGE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key CONTROL_AE_COMPENSATION_STEP; @@ -19690,6 +19822,9 @@ package android.hardware.camera2 { field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2 field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0 field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_OFF = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY = 1; // 0x1 field public static final int CONTROL_AE_STATE_CONVERGED = 2; // 0x2 field public static final int CONTROL_AE_STATE_FLASH_REQUIRED = 4; // 0x4 field public static final int CONTROL_AE_STATE_INACTIVE = 0; // 0x0 @@ -19974,6 +20109,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AE_LOCK; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AE_MODE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AE_PRECAPTURE_TRIGGER; + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AE_PRIORITY_MODE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AE_REGIONS; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key> CONTROL_AE_TARGET_FPS_RANGE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key CONTROL_AF_MODE; @@ -20067,6 +20203,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_LOCK; field @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_PRECAPTURE_TRIGGER; + field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_PRIORITY_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_REGIONS; field @NonNull public static final android.hardware.camera2.CaptureResult.Key CONTROL_AE_STATE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key> CONTROL_AE_TARGET_FPS_RANGE; @@ -20560,8 +20697,14 @@ package android.hardware.display { method @NonNull public android.hardware.display.HdrConversionMode getHdrConversionMode(); method public int getMatchContentFrameRateUserPreference(); method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler); + method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener); method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener); field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; + field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_ADDED = 1L; // 0x1L + field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_CHANGED = 4L; // 0x4L + field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_REFRESH_RATE = 8L; // 0x8L + field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_REMOVED = 2L; // 0x2L + field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_STATE = 16L; // 0x10L field public static final int MATCH_CONTENT_FRAMERATE_ALWAYS = 2; // 0x2 field public static final int MATCH_CONTENT_FRAMERATE_NEVER = 0; // 0x0 field public static final int MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY = 1; // 0x1 @@ -20914,6 +21057,8 @@ package android.hardware.usb { method public boolean hasPermission(android.hardware.usb.UsbDevice); method public boolean hasPermission(android.hardware.usb.UsbAccessory); method public android.os.ParcelFileDescriptor openAccessory(android.hardware.usb.UsbAccessory); + method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.InputStream openAccessoryInputStream(@NonNull android.hardware.usb.UsbAccessory); + method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.OutputStream openAccessoryOutputStream(@NonNull android.hardware.usb.UsbAccessory); method public android.hardware.usb.UsbDeviceConnection openDevice(android.hardware.usb.UsbDevice); method public void requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent); method public void requestPermission(android.hardware.usb.UsbAccessory, android.app.PendingIntent); @@ -21016,6 +21161,7 @@ package android.inputmethodservice { method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); method public boolean onEvaluateFullscreenMode(); method @CallSuper public boolean onEvaluateInputViewShown(); @@ -21417,6 +21563,7 @@ package android.media { method public int getId(); method public CharSequence getProductName(); method @NonNull public int[] getSampleRates(); + method @FlaggedApi("android.media.audio.speaker_layout_api") public int getSpeakerLayoutChannelMask(); method public int getType(); method public boolean isSink(); method public boolean isSource(); @@ -21905,7 +22052,7 @@ package android.media { public final class AudioPlaybackConfiguration implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioAttributes getAudioAttributes(); - method @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo(); + method @Deprecated @FlaggedApi("android.media.audio.routed_device_ids") @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -21988,6 +22135,7 @@ package android.media { method public android.media.AudioDeviceInfo getPreferredDevice(); method public int getRecordingState(); method public android.media.AudioDeviceInfo getRoutedDevice(); + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List getRoutedDevices(); method public int getSampleRate(); method public int getState(); method public int getTimestamp(@NonNull android.media.AudioTimestamp, int); @@ -22082,6 +22230,7 @@ package android.media { method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public default java.util.List getRoutedDevices(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); } @@ -22140,6 +22289,7 @@ package android.media { method public int getPositionNotificationPeriod(); method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List getRoutedDevices(); method public int getSampleRate(); method @IntRange(from=1) public int getStartThresholdInFrames(); method public int getState(); @@ -22903,6 +23053,7 @@ package android.media { method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException); method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException); method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int); + method @FlaggedApi("android.media.codec.subsession_metrics") public void onMetricsFlushed(@NonNull android.media.MediaCodec, @NonNull android.os.PersistableBundle); method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo); method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void onOutputBuffersAvailable(@NonNull android.media.MediaCodec, int, @NonNull java.util.ArrayDeque); method public abstract void onOutputFormatChanged(@NonNull android.media.MediaCodec, @NonNull android.media.MediaFormat); @@ -24307,6 +24458,7 @@ package android.media { method @NonNull public android.media.PlaybackParams getPlaybackParams(); method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List getRoutedDevices(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method @NonNull public android.media.SyncParams getSyncParams(); method @Nullable public android.media.MediaTimestamp getTimestamp(); @@ -24520,6 +24672,7 @@ package android.media { method public android.os.PersistableBundle getMetrics(); method public android.media.AudioDeviceInfo getPreferredDevice(); method public android.media.AudioDeviceInfo getRoutedDevice(); + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List getRoutedDevices(); method public android.view.Surface getSurface(); method public boolean isPrivacySensitive(); method public void pause() throws java.lang.IllegalStateException; @@ -26902,6 +27055,86 @@ package android.media.projection { } +package android.media.quality { + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public class MediaQualityContract { + } + + public static final class MediaQualityContract.PictureQuality { + field public static final String PARAMETER_BRIGHTNESS = "brightness"; + field public static final String PARAMETER_CONTRAST = "contrast"; + field public static final String PARAMETER_SATURATION = "saturation"; + field public static final String PARAMETER_SHARPNESS = "sharpness"; + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method public void createPictureProfile(@NonNull android.media.quality.PictureProfile); + method @NonNull public java.util.List getAvailablePictureProfiles(); + method @NonNull public java.util.List getParamCapabilities(@NonNull java.util.List); + method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String); + method public boolean isAutoPictureQualityEnabled(); + method public boolean isSuperResolutionEnabled(); + method public void registerPictureProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.PictureProfileCallback); + method public void removePictureProfile(@NonNull String); + method public void unregisterPictureProfileCallback(@NonNull android.media.quality.MediaQualityManager.PictureProfileCallback); + method public void updatePictureProfile(@NonNull String, @NonNull android.media.quality.PictureProfile); + } + + public abstract static class MediaQualityManager.PictureProfileCallback { + ctor public MediaQualityManager.PictureProfileCallback(); + method public void onError(int); + method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List); + method public void onPictureProfileAdded(@NonNull String, @NonNull android.media.quality.PictureProfile); + method public void onPictureProfileRemoved(@NonNull String, @NonNull android.media.quality.PictureProfile); + method public void onPictureProfileUpdated(@NonNull String, @NonNull android.media.quality.PictureProfile); + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class ParamCapability implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.Bundle getCapabilities(); + method @NonNull public String getParamName(); + method public int getParamType(); + method public boolean isSupported(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final String CAPABILITY_DEFAULT = "default"; + field public static final String CAPABILITY_ENUM = "enum"; + field public static final String CAPABILITY_MAX = "max"; + field public static final String CAPABILITY_MIN = "min"; + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_DOUBLE = 3; // 0x3 + field public static final int TYPE_INT = 1; // 0x1 + field public static final int TYPE_LONG = 2; // 0x2 + field public static final int TYPE_STRING = 4; // 0x4 + } + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class PictureProfile implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getInputId(); + method @NonNull public String getName(); + method @Nullable public String getPackageName(); + method @NonNull public android.os.PersistableBundle getParameters(); + method @Nullable public String getProfileId(); + method public int getProfileType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int ERROR_DUPLICATE = 2; // 0x2 + field public static final int ERROR_INVALID_ARGUMENT = 3; // 0x3 + field public static final int ERROR_NOT_ALLOWLISTED = 4; // 0x4 + field public static final int ERROR_NO_PERMISSION = 1; // 0x1 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + field public static final int TYPE_APPLICATION = 2; // 0x2 + field public static final int TYPE_SYSTEM = 1; // 0x1 + } + + public static final class PictureProfile.Builder { + ctor public PictureProfile.Builder(@NonNull String); + ctor public PictureProfile.Builder(@NonNull android.media.quality.PictureProfile); + method @NonNull public android.media.quality.PictureProfile build(); + method @NonNull public android.media.quality.PictureProfile.Builder setParameters(@NonNull android.os.PersistableBundle); + } + +} + package android.media.session { public final class MediaController { @@ -28451,6 +28684,8 @@ package android.media.tv.ad { method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback); method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener); method public boolean setTvView(@Nullable android.media.tv.TvView); + method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderMediaOverlay(boolean); + method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderOnTop(boolean); method public void startAdService(); method public void stopAdService(); field public static final String ERROR_KEY_ERROR_CODE = "error_code"; @@ -28723,6 +28958,8 @@ package android.media.tv.interactive { method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener); method public void setTeletextAppEnabled(boolean); method public int setTvView(@Nullable android.media.tv.TvView); + method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderMediaOverlay(boolean); + method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderOnTop(boolean); method public void startInteractiveApp(); method public void stopInteractiveApp(); field public static final String BI_INTERACTIVE_APP_KEY_ALIAS = "alias"; @@ -29762,6 +29999,7 @@ package android.net.vcn { method @NonNull public java.util.List getVcnUnderlyingNetworkPriorities(); method public boolean hasGatewayOption(int); method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled(); + field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0 } @@ -33235,6 +33473,14 @@ package android.os { method public final android.os.CountDownTimer start(); } + @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams { + ctor public CpuHeadroomParams(); + method public int getCalculationType(); + method public void setCalculationType(int); + field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 + field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 + } + public final class CpuUsageInfo implements android.os.Parcelable { method public int describeContents(); method public long getActive(); @@ -33482,6 +33728,14 @@ package android.os { method public void onProgress(long); } + @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams { + ctor public GpuHeadroomParams(); + method public int getCalculationType(); + method public void setCalculationType(int); + field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 + field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 + } + public class Handler { ctor @Deprecated public Handler(); ctor @Deprecated public Handler(@Nullable android.os.Handler.Callback); @@ -34582,8 +34836,6 @@ package android.os { method public static android.os.VibrationEffect createWaveform(long[], int[], int); method public int describeContents(); method @NonNull public static android.os.VibrationEffect.Composition startComposition(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff field public static final int EFFECT_CLICK = 0; // 0x0 @@ -34596,7 +34848,10 @@ package android.os { method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int); method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float); method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int, int); method @NonNull public android.os.VibrationEffect compose(); + field @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") public static final int DELAY_TYPE_PAUSE = 0; // 0x0 + field @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") public static final int DELAY_TYPE_RELATIVE_START_OFFSET = 1; // 0x1 field public static final int PRIMITIVE_CLICK = 1; // 0x1 field public static final int PRIMITIVE_LOW_TICK = 8; // 0x8 field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6 @@ -34608,8 +34863,10 @@ package android.os { } @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public static final class VibrationEffect.WaveformEnvelopeBuilder { + ctor public VibrationEffect.WaveformEnvelopeBuilder(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect.WaveformEnvelopeBuilder addControlPoint(@FloatRange(from=0, to=1) float, @FloatRange(from=0) float, int); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect build(); + method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.VibrationEffect.WaveformEnvelopeBuilder setInitialFrequencyHz(@FloatRange(from=0) float); } public abstract class Vibrator { @@ -34619,12 +34876,9 @@ package android.os { method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public boolean areEnvelopeEffectsSupported(); method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.vibrator.VibratorEnvelopeEffectInfo getEnvelopeEffectInfo(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile(); method public int getId(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectControlPointDurationMillis(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectDurationMillis(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectSize(); - method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMinEnvelopeEffectControlPointDurationMillis(); method @NonNull public int[] getPrimitiveDurations(@NonNull int...); method public float getQFactor(); method public float getResonantFrequency(); @@ -34732,6 +34986,10 @@ package android.os.health { } public class SystemHealthManager { + method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getCpuHeadroom(@Nullable android.os.CpuHeadroomParams); + method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getCpuHeadroomMinIntervalMillis(); + method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getGpuHeadroom(@Nullable android.os.GpuHeadroomParams); + method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getGpuHeadroomMinIntervalMillis(); method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer>); method public android.os.health.HealthStats takeMyUidSnapshot(); @@ -34975,6 +35233,16 @@ package android.os.strictmode { package android.os.vibrator { + @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorEnvelopeEffectInfo implements android.os.Parcelable { + method public int describeContents(); + method public long getMaxControlPointDurationMillis(); + method public long getMaxDurationMillis(); + method public int getMaxSize(); + method public long getMinControlPointDurationMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorFrequencyProfile { method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.util.SparseArray getFrequenciesOutputAcceleration(); method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.util.Range getFrequencyRange(float); @@ -37758,6 +38026,7 @@ package android.provider { field public static final String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS"; field public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS"; field @Deprecated public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL"; + field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS = "android.settings.FIRST_DAY_OF_WEEK_SETTINGS"; field public static final String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS"; field public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS"; field public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS"; @@ -37778,6 +38047,7 @@ package android.provider { field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING"; field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; + field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS = "android.settings.MEASUREMENT_SYSTEM_SETTINGS"; field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS"; field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; @@ -37788,12 +38058,14 @@ package android.provider { field public static final String ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS = "android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS"; field public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS"; + field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = "android.settings.NUMBERING_SYSTEM_SETTINGS"; field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS"; field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI"; field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS"; field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS"; field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS"; + field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_REGION_SETTINGS = "android.settings.REGION_SETTINGS"; field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA"; field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL"; @@ -37809,6 +38081,7 @@ package android.provider { field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; field @Deprecated public static final String ACTION_STORAGE_VOLUME_ACCESS_SETTINGS = "android.settings.STORAGE_VOLUME_ACCESS_SETTINGS"; field public static final String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS"; + field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_TEMPERATURE_UNIT_SETTINGS = "android.settings.TEMPERATURE_UNIT_SETTINGS"; field public static final String ACTION_USAGE_ACCESS_SETTINGS = "android.settings.USAGE_ACCESS_SETTINGS"; field public static final String ACTION_USER_DICTIONARY_SETTINGS = "android.settings.USER_DICTIONARY_SETTINGS"; field public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE = "android.settings.VOICE_CONTROL_AIRPLANE_MODE"; @@ -40588,6 +40861,7 @@ package android.service.autofill { method public int describeContents(); method @Deprecated @Nullable public android.os.Bundle getClientState(); method @Nullable public java.util.List getEvents(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -40597,10 +40871,12 @@ package android.service.autofill { method @Nullable public android.os.Bundle getClientState(); method @Nullable public String getDatasetId(); method @NonNull public java.util.Map getFieldsClassification(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") @Nullable public android.view.autofill.AutofillId getFocusedId(); method @NonNull public java.util.Set getIgnoredDatasetIds(); method @NonNull public java.util.Map> getManuallyEnteredField(); method public int getNoSaveUiReason(); method @NonNull public java.util.Set getSelectedDatasetIds(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") @NonNull public java.util.Set getShownDatasetIds(); method public int getType(); method public int getUiType(); field public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; // 0x6 @@ -40609,6 +40885,7 @@ package android.service.autofill { field public static final int NO_SAVE_UI_REASON_NONE = 0; // 0x0 field public static final int NO_SAVE_UI_REASON_NO_SAVE_INFO = 1; // 0x1 field public static final int NO_SAVE_UI_REASON_NO_VALUE_CHANGED = 4; // 0x4 + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; // 0x7 field public static final int NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG = 2; // 0x2 field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2 field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4 @@ -40617,6 +40894,7 @@ package android.service.autofill { field public static final int TYPE_DATASET_SELECTED = 0; // 0x0 field public static final int TYPE_SAVE_SHOWN = 3; // 0x3 field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6 + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4 field public static final int UI_TYPE_DIALOG = 3; // 0x3 field public static final int UI_TYPE_INLINE = 2; // 0x2 field public static final int UI_TYPE_MENU = 1; // 0x1 @@ -40636,7 +40914,7 @@ package android.service.autofill { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2 field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1 - field public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40 + field @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public static final int FLAG_SUPPORTS_FILL_DIALOG = 64; // 0x40 } public final class FillResponse implements android.os.Parcelable { @@ -41902,6 +42180,7 @@ package android.service.quickaccesswallet { public abstract class QuickAccessWalletService extends android.app.Service { ctor public QuickAccessWalletService(); + method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") @Nullable public android.app.PendingIntent getGestureTargetActivityPendingIntent(); method @Nullable public android.app.PendingIntent getTargetActivityPendingIntent(); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest); @@ -46579,6 +46858,8 @@ package android.telephony { method public long getDataUsageBytes(); method public long getDataUsageTime(); method @NonNull public int[] getNetworkTypes(); + method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @Nullable public java.time.ZonedDateTime getPlanEndDate(); + method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public int getSubscriptionStatus(); method @Nullable public CharSequence getSummary(); method @Nullable public CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); @@ -46589,6 +46870,11 @@ package android.telephony { field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0 field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2 field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_ACTIVE = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_INACTIVE = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_TRIAL = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0; // 0x0 field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL } @@ -46600,6 +46886,7 @@ package android.telephony { method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int); method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long); method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@NonNull int[]); + method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @NonNull public android.telephony.SubscriptionPlan.Builder setSubscriptionStatus(int); method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence); method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence); } @@ -49579,6 +49866,14 @@ package android.text.style { method public abstract void updateMeasureState(@NonNull android.text.TextPaint); } + @FlaggedApi("android.view.inputmethod.writing_tools") public final class NoWritingToolsSpan implements android.text.ParcelableSpan { + ctor public NoWritingToolsSpan(); + method public int describeContents(); + method public int getSpanTypeId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public interface ParagraphStyle { } @@ -51330,6 +51625,7 @@ package android.view { method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl); method public default int getBufferTransformHint(); method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken(); + method @FlaggedApi("com.android.window.flags.jank_api") @NonNull public default android.view.SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.OnJankDataListener); method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public default void setChildBoundingInsets(@NonNull android.graphics.Rect); method public default void setTouchableRegion(@Nullable android.graphics.Region); @@ -51587,6 +51883,7 @@ package android.view { field public static final int DEADLINE = 13; // 0xd field public static final int DRAW_DURATION = 4; // 0x4 field public static final int FIRST_DRAW_FRAME = 9; // 0x9 + field @FlaggedApi("com.android.window.flags.jank_api") public static final int FRAME_TIMELINE_VSYNC_ID = 14; // 0xe field public static final int GPU_DURATION = 12; // 0xc field public static final int INPUT_HANDLING_DURATION = 1; // 0x1 field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa @@ -53024,6 +53321,26 @@ package android.view { method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl); } + @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.JankData { + method public long getActualAppFrameTimeNanos(); + method public int getJankType(); + method public long getScheduledAppFrameTimeNanos(); + method public long getVsyncId(); + field public static final int JANK_APPLICATION = 2; // 0x2 + field public static final int JANK_COMPOSER = 1; // 0x1 + field public static final int JANK_NONE = 0; // 0x0 + field public static final int JANK_OTHER = 4; // 0x4 + } + + @FlaggedApi("com.android.window.flags.jank_api") public static interface SurfaceControl.OnJankDataListener { + method public void onJankDataAvailable(@NonNull java.util.List); + } + + @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.OnJankDataListenerRegistration { + method public void flush(); + method public void removeAfter(long); + } + public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable { ctor public SurfaceControl.Transaction(); method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener); @@ -53053,6 +53370,7 @@ package android.view { method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setFrameTimeline(long); method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int); + method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.view.SurfaceControl.Transaction setLuts(@NonNull android.view.SurfaceControl, @Nullable android.hardware.DisplayLuts); method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean); method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float); method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float); @@ -53728,6 +54046,7 @@ package android.view { method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void removeOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener); + method @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public void reportAppJankStats(@NonNull android.app.jank.AppJankStats); method public void requestApplyInsets(); method @Deprecated public void requestFitSystemWindows(); method public final boolean requestFocus(); @@ -53981,6 +54300,7 @@ package android.view { field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000 field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1 field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2 + field @FlaggedApi("com.android.window.flags.supports_drag_assistant_to_multiwindow") public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 16384; // 0x4000 field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200 field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 8192; // 0x2000 field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0 @@ -54725,6 +55045,8 @@ package android.view { method public abstract void setTransformation(android.graphics.Matrix); method public abstract void setVisibility(int); method public abstract void setWebDomain(@Nullable String); + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; } public abstract static class ViewStructure.HtmlInfo { @@ -56447,6 +56769,11 @@ package android.view.autofill { public final class AutofillId implements android.os.Parcelable { method @NonNull public static android.view.autofill.AutofillId create(@NonNull android.view.View, int); method public int describeContents(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getAutofillVirtualId(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getSessionId(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public int getViewId(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isInAutofillSession(); + method @FlaggedApi("android.service.autofill.autofill_w_metrics") public boolean isVirtual(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -56475,13 +56802,13 @@ package android.view.autofill { method public void notifyViewExited(@NonNull android.view.View, int); method public void notifyViewVisibilityChanged(@NonNull android.view.View, boolean); method public void notifyViewVisibilityChanged(@NonNull android.view.View, int, boolean); - method public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray); + method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray); method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); method public void requestAutofill(@NonNull android.view.View); method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect); method public void setUserData(@Nullable android.service.autofill.UserData); - method public boolean showAutofillDialog(@NonNull android.view.View); - method public boolean showAutofillDialog(@NonNull android.view.View, int); + method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View); + method @Deprecated @FlaggedApi("android.service.autofill.fill_dialog_improvements") public boolean showAutofillDialog(@NonNull android.view.View, int); method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; @@ -56834,6 +57161,7 @@ package android.view.inputmethod { ctor public EditorInfo(); method public int describeContents(); method public void dump(android.util.Printer, String); + method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") @Nullable public android.view.autofill.AutofillId getAutofillId(); method @Nullable public CharSequence getInitialSelectedText(int); method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int); method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int); @@ -56842,13 +57170,16 @@ package android.view.inputmethod { method @NonNull public java.util.Set> getSupportedHandwritingGesturePreviews(); method @NonNull public java.util.List> getSupportedHandwritingGestures(); method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled(); + method @FlaggedApi("android.view.inputmethod.writing_tools") public boolean isWritingToolsEnabled(); method public final void makeCompatible(int); + method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") public void setAutofillId(@Nullable android.view.autofill.AutofillId); method public void setInitialSurroundingSubText(@NonNull CharSequence, int); method public void setInitialSurroundingText(@NonNull CharSequence); method public void setInitialToolType(int); method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean); method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set>); method public void setSupportedHandwritingGestures(@NonNull java.util.List>); + method @FlaggedApi("android.view.inputmethod.writing_tools") public void setWritingToolsEnabled(boolean); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int IME_ACTION_DONE = 6; // 0x6 diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index 1dcafd1d80ea4e8858027ac956bf2d32a1f55f46..ad5bd31828e030f0a9d97b242b02ebd7766c5e0d 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -1,4 +1,10 @@ // Baseline format: 1.0 +ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE: + Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE` +ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER: + Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER` + + BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED: Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: @@ -383,6 +389,36 @@ DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match +FlaggedApiLiteral: android.Manifest.permission#BIND_APP_FUNCTION_SERVICE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER). +FlaggedApiLiteral: android.Manifest.permission#BIND_TV_AD_SERVICE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.media.tv.flags.Flags.FLAG_ENABLE_AD_SERVICE_FW). +FlaggedApiLiteral: android.Manifest.permission#MANAGE_DEVICE_POLICY_THREAD_NETWORK: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#QUERY_ADVANCED_PROTECTION_MODE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.security.Flags.FLAG_AAPM_API). +FlaggedApiLiteral: android.Manifest.permission#RANGING: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_RANGING_PERMISSION_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#REQUEST_OBSERVE_DEVICE_UUID_PRESENCE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.companion.Flags.FLAG_DEVICE_PRESENCE). +FlaggedApiLiteral: android.R.attr#adServiceTypes: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.media.tv.flags.Flags.FLAG_ENABLE_AD_SERVICE_FW). +FlaggedApiLiteral: android.R.attr#intentMatchingFlags: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.security.Flags.FLAG_ENABLE_INTENT_MATCHING_FLAGS). +FlaggedApiLiteral: android.R.attr#languageSettingsActivity: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API). +FlaggedApiLiteral: android.R.attr#optional: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_SDK_LIB_INDEPENDENCE). +FlaggedApiLiteral: android.R.attr#supplementalDescription: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION). +FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS). +FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS). +FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_SUCCESS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS). + + InvalidNullabilityOverride: android.app.Notification.TvExtender#extend(android.app.Notification.Builder) parameter #0: Invalid nullability on parameter `builder` in method `extend`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(android.content.Intent) parameter #0: @@ -1155,6 +1191,10 @@ UnflaggedApi: android.R.dimen#system_corner_radius_xlarge: New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge UnflaggedApi: android.R.dimen#system_corner_radius_xsmall: New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall +UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum: + Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum +UnflaggedApi: android.R.string#status_bar_notification_info_overflow: + Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR: New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID: @@ -1447,7 +1487,6 @@ UnflaggedApi: android.graphics.text.PositionedGlyphs#getItalicOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int) UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int) - UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR: New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER: diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index bc73220cb4c174791d949dba37466db6230993fc..1a949d84c0527f470efccdac2f88cd4549866572 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -251,6 +251,10 @@ package android.media.session { package android.net { + @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava { + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers(); + } + public class LocalSocket implements java.io.Closeable { ctor public LocalSocket(@NonNull java.io.FileDescriptor); } @@ -310,6 +314,25 @@ package android.net.netstats { } +package android.net.vcn { + + @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo { + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents(); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions(); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds(); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder { + ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder(); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build(); + method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int); + } + +} + package android.net.wifi { public final class WifiMigration { @@ -379,6 +402,13 @@ package android.os { field public static final int DEVICE_INITIAL_SDK_INT; } + public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { + method @FlaggedApi("android.os.enable_has_binders") public int hasBinders(); + field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_NOT_PRESENT = 0; // 0x0 + field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_PRESENT = 1; // 0x1 + field @FlaggedApi("android.os.enable_has_binders") public static final int STATUS_BINDERS_UNKNOWN = 2; // 0x2 + } + public class Handler { method @FlaggedApi("android.os.mainline_vcn_platform_api") public final boolean hasMessagesOrCallbacks(); method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeCallbacksAndEqualMessages(@Nullable Object); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8954f8e70157af48efb492e9de0e57553ad819bc..bc0037d9318f7524edac0c84f3d837376a783a8a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -46,6 +46,7 @@ package android { field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; + field @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled") public static final String BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS = "android.permission.BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; field public static final String BIND_AMBIENT_CONTEXT_DETECTION_SERVICE = "android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; @@ -56,6 +57,7 @@ package android { field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String BIND_DEPENDENCY_INSTALLER = "android.permission.BIND_DEPENDENCY_INSTALLER"; field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE"; field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE"; @@ -64,6 +66,7 @@ package android { field @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"; field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE"; field public static final String BIND_FIELD_CLASSIFICATION_SERVICE = "android.permission.BIND_FIELD_CLASSIFICATION_SERVICE"; + field @FlaggedApi("android.security.afl_api") public static final String BIND_FORENSIC_EVENT_TRANSPORT_SERVICE = "android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE"; field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE"; field public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission.BIND_HOTWORD_DETECTION_SERVICE"; field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; @@ -163,6 +166,7 @@ package android { field public static final String HDMI_CEC = "android.permission.HDMI_CEC"; field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"; field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES"; field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM"; field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES"; @@ -208,6 +212,7 @@ package android { field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"; field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field @FlaggedApi("android.security.afl_api") public static final String MANAGE_FORENSIC_STATE = "android.permission.MANAGE_FORENSIC_STATE"; field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY"; field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE = "android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"; @@ -303,6 +308,7 @@ package android { field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG"; field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE"; + field @FlaggedApi("android.security.afl_api") public static final String READ_FORENSIC_STATE = "android.permission.READ_FORENSIC_STATE"; field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA"; field @FlaggedApi("android.content.pm.get_resolved_apk_path") public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS"; field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS"; @@ -347,6 +353,7 @@ package android { field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM"; + field @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled") public static final String RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS = "android.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT"; @@ -526,6 +533,7 @@ package android { field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller; field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 @@ -707,8 +715,8 @@ package android.app { field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected"; - field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation"; - field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; + field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation"; + field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data"; field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; @@ -1270,13 +1278,21 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); + method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray getBitmapCrops(int); + method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point); method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); + method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean); method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int); method public void setDisplayOffset(android.os.IBinder, int, int); + method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray, boolean, int) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName); method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float); + field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_LANDSCAPE = 1; // 0x1 + field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_PORTRAIT = 0; // 0x0 + field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_LANDSCAPE = 3; // 0x3 + field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_PORTRAIT = 2; // 0x2 } } @@ -1326,8 +1342,10 @@ package android.app.admin { public class DevicePolicyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback(); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; + method @Deprecated @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; + method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent); + method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeCreateManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams, @NonNull android.os.UserHandle) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); @@ -1358,6 +1376,7 @@ package android.app.admin { method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; + method @FlaggedApi("android.app.admin.flags.remove_managed_profile_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean removeManagedProfile(); method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set) throws android.content.pm.PackageManager.NameNotFoundException; @@ -1367,7 +1386,8 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); - method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @Deprecated @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(boolean, @Nullable android.os.PersistableBundle); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification(); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; @@ -1920,6 +1940,7 @@ package android.app.backup { method public android.os.IBinder getBinder(); method public long getCurrentRestoreSet(); method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor); + method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List, int); method public int getRestoreData(android.os.ParcelFileDescriptor); method public int getTransportFlags(); method public int initializeDevice(); @@ -4573,6 +4594,24 @@ package android.content.pm { } +package android.content.pm.dependencyinstaller { + + @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable { + method public int describeContents(); + method public void onAllDependenciesResolved(@NonNull int[]); + method public void onFailureToResolveAllDependencies(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service { + ctor public DependencyInstallerService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method public abstract void onDependenciesRequired(@NonNull java.util.List, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback); + } + +} + package android.content.pm.dex { public class ArtManager { @@ -5033,15 +5072,27 @@ package android.hardware.biometrics { package android.hardware.camera2 { + public final class CameraCharacteristics extends android.hardware.camera2.CameraMetadata> { + field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key SHARED_SESSION_CONFIGURATION; + } + public abstract class CameraDevice implements java.lang.AutoCloseable { method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1 field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_OPERATION_MODE_SHARED = 2; // 0x2 field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000 } + public abstract static class CameraDevice.StateCallback { + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onClientSharedAccessPriorityChanged(@NonNull android.hardware.camera2.CameraDevice, boolean); + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public void onOpenedInSharedMode(@NonNull android.hardware.camera2.CameraDevice, boolean); + } + public final class CameraManager { + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public boolean isCameraDeviceSharingSupported(@NonNull String) throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openSharedCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; } public abstract static class CameraManager.AvailabilityCallback { @@ -5049,6 +5100,12 @@ package android.hardware.camera2 { method @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER) public void onCameraOpened(@NonNull String, @NonNull String); } + @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract class CameraSharedCaptureSession extends android.hardware.camera2.CameraCaptureSession { + ctor public CameraSharedCaptureSession(); + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract int startStreaming(@NonNull java.util.List, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.CaptureCallback) throws android.hardware.camera2.CameraAccessException; + method @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public abstract void stopStreaming() throws android.hardware.camera2.CameraAccessException; + } + } package android.hardware.camera2.extension { @@ -5157,6 +5214,142 @@ package android.hardware.camera2.params { field public static final int ROTATION_90 = 1; // 0x1 } + public final class SessionConfiguration implements android.os.Parcelable { + field @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public static final int SESSION_SHARED = 2; // 0x2 + } + + @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration { + method @Nullable public android.graphics.ColorSpace getColorSpace(); + method @NonNull public java.util.List getOutputStreamsInformation(); + } + + public static final class SharedSessionConfiguration.SharedOutputConfiguration { + method public int getDataspace(); + method public int getFormat(); + method public int getMirrorMode(); + method @Nullable public String getPhysicalCameraId(); + method @NonNull public android.util.Size getSize(); + method public long getStreamUseCase(); + method public int getSurfaceType(); + method public int getTimestampBase(); + method public long getUsage(); + method public boolean isReadoutTimestampEnabled(); + } + +} + +package android.hardware.contexthub { + + @FlaggedApi("android.chre.flags.offload_api") public class HubDiscoveryInfo { + method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo(); + method @Nullable public android.hardware.contexthub.HubServiceInfo getHubServiceInfo(); + } + + @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint { + method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback(); + method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback(); + method @NonNull public java.util.Collection getServiceInfoCollection(); + method @Nullable public String getTag(); + method public int getVersion(); + } + + public static final class HubEndpoint.Builder { + ctor public HubEndpoint.Builder(@NonNull android.content.Context); + method @NonNull public android.hardware.contexthub.HubEndpoint build(); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String); + } + + @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier(); + method @NonNull public String getName(); + method @NonNull public java.util.Collection getRequiredPermissions(); + method @NonNull public java.util.Collection getServiceInfoCollection(); + method @Nullable public String getTag(); + method public int getType(); + method public int getVersion(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_APP = 2; // 0x2 + field public static final int TYPE_FRAMEWORK = 1; // 0x1 + field public static final int TYPE_HUB_ENDPOINT = 5; // 0x5 + field public static final int TYPE_NANOAPP = 4; // 0x4 + field public static final int TYPE_NATIVE = 3; // 0x3 + } + + public static class HubEndpointInfo.HubEndpointIdentifier { + method public long getEndpoint(); + method public long getHub(); + } + + @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable { + method public void close(); + method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo(); + method @NonNull public android.hardware.location.ContextHubTransaction sendMessage(@NonNull android.hardware.contexthub.HubMessage); + } + + @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult { + method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult accept(); + method @Nullable public String getReason(); + method public boolean isAccepted(); + method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String); + } + + @FlaggedApi("android.chre.flags.offload_api") public final class HubMessage implements android.os.Parcelable { + method @NonNull public static android.hardware.contexthub.HubMessage createMessage(int, @NonNull byte[]); + method @NonNull public static android.hardware.contexthub.HubMessage createMessage(int, @NonNull byte[], @NonNull android.hardware.contexthub.HubMessage.DeliveryParams); + method public int describeContents(); + method @NonNull public byte[] getMessageBody(); + method public int getMessageType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static class HubMessage.DeliveryParams { + method public boolean isResponseRequired(); + method @NonNull public static android.hardware.contexthub.HubMessage.DeliveryParams makeBasic(); + method @NonNull public android.hardware.contexthub.HubMessage.DeliveryParams setResponseRequired(boolean); + } + + @FlaggedApi("android.chre.flags.offload_api") public final class HubServiceInfo implements android.os.Parcelable { + ctor public HubServiceInfo(@NonNull String, int, int, int, @NonNull android.os.ParcelableHolder); + method public int describeContents(); + method @NonNull public android.os.ParcelableHolder getExtendedInfo(); + method public int getFormat(); + method public int getMajorVersion(); + method public int getMinorVersion(); + method @NonNull public String getServiceDescriptor(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int FORMAT_AIDL = 1; // 0x1 + field public static final int FORMAT_CUSTOM = 0; // 0x0 + field public static final int FORMAT_PW_RPC_PROTOBUF = 2; // 0x2 + } + + public static final class HubServiceInfo.Builder { + ctor public HubServiceInfo.Builder(@NonNull String, int, int, int); + method @NonNull public android.hardware.contexthub.HubServiceInfo build(); + method @NonNull public android.hardware.contexthub.HubServiceInfo.Builder setExtendedInfo(@Nullable android.os.Parcelable); + } + + @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback { + method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); + method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo); + method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); + field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4 + field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3 + field public static final int REASON_UNSPECIFIED = 0; // 0x0 + } + + @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback { + method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage); + } + } package android.hardware.devicestate { @@ -5289,6 +5482,7 @@ package android.hardware.display { method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration(); method public android.util.Pair getMinimumBrightnessCurve(); method public android.graphics.Point getStableDisplaySize(); + method @FlaggedApi("com.android.server.display.feature.flags.is_always_on_available_api") public boolean isAlwaysOnDisplayCurrentlyAvailable(); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); @@ -6150,6 +6344,8 @@ package android.hardware.location { method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); + method @FlaggedApi("android.chre.flags.offload_api") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public java.util.List findEndpoints(long); + method @FlaggedApi("android.chre.flags.offload_api") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public java.util.List findEndpoints(@NonNull String); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] findNanoAppOnHub(int, @NonNull android.hardware.location.NanoAppFilter); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] getContextHubHandles(); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubInfo getContextHubInfo(int); @@ -6157,13 +6353,17 @@ package android.hardware.location { method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo); method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); field public static final int AUTHORIZATION_DENIED = 0; // 0x0 field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1 field public static final int AUTHORIZATION_GRANTED = 2; // 0x2 @@ -6219,6 +6419,8 @@ package android.hardware.location { field public static final int RESULT_SUCCESS = 0; // 0x0 field public static final int TYPE_DISABLE_NANOAPP = 3; // 0x3 field public static final int TYPE_ENABLE_NANOAPP = 2; // 0x2 + field @FlaggedApi("android.chre.flags.offload_api") public static final int TYPE_HUB_MESSAGE_DEFAULT = 6; // 0x6 + field @FlaggedApi("android.chre.flags.offload_api") public static final int TYPE_HUB_MESSAGE_REQUIRES_RESPONSE = 7; // 0x7 field public static final int TYPE_LOAD_NANOAPP = 0; // 0x0 field public static final int TYPE_QUERY_NANOAPPS = 4; // 0x4 field public static final int TYPE_RELIABLE_MESSAGE = 5; // 0x5 @@ -7452,6 +7654,7 @@ package android.media { } public final class AudioPlaybackConfiguration implements android.os.Parcelable { + method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List getAudioDeviceInfos(); method public int getChannelMask(); method public int getClientPid(); method public int getClientUid(); @@ -7606,7 +7809,7 @@ package android.media { public final class MediaCas implements java.lang.AutoCloseable { method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean); - method @FlaggedApi("com.android.media.flags.update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int); + method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int); } public final class MediaCodec { @@ -7945,6 +8148,25 @@ package android.media.musicrecognition { } +package android.media.quality { + + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method @NonNull public java.util.List getPictureProfileAllowList(); + method @NonNull public java.util.List getPictureProfilePackageNames(); + method @NonNull public java.util.List getPictureProfilesByPackage(@NonNull String); + method public void setAutoPictureQualityEnabled(boolean); + method public void setPictureProfileAllowList(@NonNull java.util.List); + method public void setSuperResolutionEnabled(boolean); + } + + public static final class PictureProfile.Builder { + method @NonNull public android.media.quality.PictureProfile.Builder setInputId(@NonNull String); + method @NonNull public android.media.quality.PictureProfile.Builder setPackageName(@NonNull String); + method @NonNull public android.media.quality.PictureProfile.Builder setProfileType(int); + } + +} + package android.media.session { public final class MediaSessionManager { @@ -8235,6 +8457,104 @@ package android.media.tv { method public void onSetMain(boolean); } + @FlaggedApi("android.media.tv.flags.tif_extension_standardization") public final class TvInputServiceExtensionManager { + method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public int registerExtensionIBinder(@NonNull String, @NonNull android.os.IBinder); + field public static final String IANALOG_ATTRIBUTE_INTERFACE = "android.media.tv.extension.analog.IAnalogAttributeInterface"; + field public static final String IANALOG_AUDIO_INFO = "android.media.tv.extension.signal.IAnalogAudioInfo"; + field public static final String IAUDIO_SIGNAL_INFO = "android.media.tv.extension.signal.IAudioSignalInfo"; + field public static final String IAUDIO_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IAudioSignalInfoListener"; + field public static final String IBROADCAST_TIME = "android.media.tv.extension.time.IBroadcastTime"; + field public static final String ICAM_APP_INFO_LISTENER = "android.media.tv.extension.cam.ICamAppInfoListener"; + field public static final String ICAM_APP_INFO_SERVICE = "android.media.tv.extension.cam.ICamAppInfoService"; + field public static final String ICAM_DRM_INFO_LISTENER = "android.media.tv.extension.cam.ICamDrmInfoListener"; + field public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = "android.media.tv.extension.cam.ICamHostControlAskReleaseReplyCallback"; + field public static final String ICAM_HOST_CONTROL_INFO_LISTENER = "android.media.tv.extension.cam.ICamHostControlInfoListener"; + field public static final String ICAM_HOST_CONTROL_SERVICE = "android.media.tv.extension.cam.ICamHostControlService"; + field public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = "android.media.tv.extension.cam.ICamHostControlTuneQuietlyFlag"; + field public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = "android.media.tv.extension.cam.ICamHostControlTuneQuietlyFlagListener"; + field public static final String ICAM_INFO_LISTENER = "android.media.tv.extension.cam.ICamInfoListener"; + field public static final String ICAM_MONITORING_SERVICE = "android.media.tv.extension.cam.ICamMonitoringService"; + field public static final String ICAM_PIN_CAPABILITY_LISTENER = "android.media.tv.extension.cam.ICamPinCapabilityListener"; + field public static final String ICAM_PIN_SERVICE = "android.media.tv.extension.cam.ICamPinService"; + field public static final String ICAM_PIN_STATUS_LISTENER = "android.media.tv.extension.cam.ICamPinStatusListener"; + field public static final String ICAM_PROFILE_INTERFACE = "android.media.tv.extension.cam.ICamProfileInterface"; + field public static final String ICHANNEL_LIST_TRANSFER = "android.media.tv.extension.servicedb.IChannelListTransfer"; + field public static final String ICHANNEL_TUNED_INTERFACE = "android.media.tv.extension.tune.IChannelTunedInterface"; + field public static final String ICHANNEL_TUNED_LISTENER = "android.media.tv.extension.tune.IChannelTunedListener"; + field public static final String ICI_OPERATOR_INTERFACE = "android.media.tv.extension.cam.ICiOperatorInterface"; + field public static final String ICI_OPERATOR_LISTENER = "android.media.tv.extension.cam.ICiOperatorListener"; + field public static final String ICLIENT_TOKEN = "android.media.tv.extension.clienttoken.IClientToken"; + field public static final String ICONTENT_CONTROL_SERVICE = "android.media.tv.extension.cam.IContentControlService"; + field public static final String IDATA_SERVICE_SIGNAL_INFO = "android.media.tv.extension.teletext.IDataServiceSignalInfo"; + field public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = "android.media.tv.extension.teletext.IDataServiceSignalInfoListener"; + field public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = "android.media.tv.extension.pvr.IDeleteRecordedContentsCallback"; + field public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = "android.media.tv.extension.rating.IDownloadableRatingTableMonitor"; + field public static final String IENTER_MENU_ERROR_CALLBACK = "android.media.tv.extension.cam.IEnterMenuErrorCallback"; + field public static final String IEVENT_DOWNLOAD = "android.media.tv.extension.event.IEventDownload"; + field public static final String IEVENT_DOWNLOAD_LISTENER = "android.media.tv.extension.event.IEventDownloadListener"; + field public static final String IEVENT_DOWNLOAD_SESSION = "android.media.tv.extension.event.IEventDownloadSession"; + field public static final String IEVENT_MONITOR = "android.media.tv.extension.event.IEventMonitor"; + field public static final String IEVENT_MONITOR_LISTENER = "android.media.tv.extension.event.IEventMonitorListener"; + field public static final String IFAVORITE_NETWORK = "android.media.tv.extension.scan.IFavoriteNetwork"; + field public static final String IFAVORITE_NETWORK_LISTENER = "android.media.tv.extension.scan.IFavoriteNetworkListener"; + field public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = "android.media.tv.extension.pvr.IGetInfoRecordedContentsCallback"; + field public static final String IHDMI_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IHdmiSignalInfoListener"; + field public static final String IHDMI_SIGNAL_INTERFACE = "android.media.tv.extension.signal.IHdmiSignalInterface"; + field public static final String IHDPLUS_INFO = "android.media.tv.extension.scan.IHDPlusInfo"; + field public static final String ILCNV2_CHANNEL_LIST = "android.media.tv.extension.scan.ILcnV2ChannelList"; + field public static final String ILCNV2_CHANNEL_LIST_LISTENER = "android.media.tv.extension.scan.ILcnV2ChannelListListener"; + field public static final String ILCN_CONFLICT = "android.media.tv.extension.scan.ILcnConflict"; + field public static final String ILCN_CONFLICT_LISTENER = "android.media.tv.extension.scan.ILcnConflictListener"; + field public static final String IMMI_INTERFACE = "android.media.tv.extension.cam.IMmiInterface"; + field public static final String IMMI_SESSION = "android.media.tv.extension.cam.IMmiSession"; + field public static final String IMMI_STATUS_CALLBACK = "android.media.tv.extension.cam.IMmiStatusCallback"; + field public static final String IMUX_TUNE = "android.media.tv.extension.tune.IMuxTune"; + field public static final String IMUX_TUNE_SESSION = "android.media.tv.extension.tune.IMuxTuneSession"; + field public static final String IOAD_UPDATE_INTERFACE = "android.media.tv.extension.oad.IOadUpdateInterface"; + field public static final String IOPERATOR_DETECTION = "android.media.tv.extension.scan.IOperatorDetection"; + field public static final String IOPERATOR_DETECTION_LISTENER = "android.media.tv.extension.scan.IOperatorDetectionListener"; + field public static final String IPMT_RATING_INTERFACE = "android.media.tv.extension.rating.IPmtRatingInterface"; + field public static final String IPMT_RATING_LISTENER = "android.media.tv.extension.rating.IPmtRatingListener"; + field public static final String IPROGRAM_INFO = "android.media.tv.extension.rating.IProgramInfo"; + field public static final String IPROGRAM_INFO_LISTENER = "android.media.tv.extension.rating.IProgramInfoListener"; + field public static final String IRATING_INTERFACE = "android.media.tv.extension.rating.IRatingInterface"; + field public static final String IRECORDED_CONTENTS = "android.media.tv.extension.pvr.IRecordedContents"; + field public static final String IREGION_CHANNEL_LIST = "android.media.tv.extension.scan.IRegionChannelList"; + field public static final String IREGION_CHANNEL_LIST_LISTENER = "android.media.tv.extension.scan.IRegionChannelListListener"; + field public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = "android.media.tv.extension.scanbsu.IScanBackgroundServiceUpdate"; + field public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = "android.media.tv.extension.scanbsu.IScanBackgroundServiceUpdateListener"; + field public static final String ISCAN_INTERFACE = "android.media.tv.extension.scan.IScanInterface"; + field public static final String ISCAN_LISTENER = "android.media.tv.extension.scan.IScanListener"; + field public static final String ISCAN_SAT_SEARCH = "android.media.tv.extension.scan.IScanSatSearch"; + field public static final String ISCAN_SESSION = "android.media.tv.extension.scan.IScanSession"; + field public static final String ISCREEN_MODE_SETTINGS = "android.media.tv.extension.screenmode.IScreenModeSettings"; + field public static final String ISERVICE_LIST = "android.media.tv.extension.servicedb.IServiceList"; + field public static final String ISERVICE_LIST_EDIT = "android.media.tv.extension.servicedb.IServiceListEdit"; + field public static final String ISERVICE_LIST_EDIT_LISTENER = "android.media.tv.extension.servicedb.IServiceListEditListener"; + field public static final String ISERVICE_LIST_EXPORT_LISTENER = "android.media.tv.extension.servicedb.IServiceListExportListener"; + field public static final String ISERVICE_LIST_EXPORT_SESSION = "android.media.tv.extension.servicedb.IServiceListExportSession"; + field public static final String ISERVICE_LIST_IMPORT_LISTENER = "android.media.tv.extension.servicedb.IServiceListImportListener"; + field public static final String ISERVICE_LIST_IMPORT_SESSION = "android.media.tv.extension.servicedb.IServiceListImportSession"; + field public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = "android.media.tv.extension.servicedb.IServiceListSetChannelListListener"; + field public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = "android.media.tv.extension.servicedb.IServiceListSetChannelListSession"; + field public static final String ISERVICE_LIST_TRANSFER_INTERFACE = "android.media.tv.extension.servicedb.IServiceListTransferInterface"; + field public static final String ITARGET_REGION = "android.media.tv.extension.scan.ITargetRegion"; + field public static final String ITARGET_REGION_LISTENER = "android.media.tv.extension.scan.ITargetRegionListener"; + field public static final String ITELETEXT_PAGE_SUB_CODE = "android.media.tv.extension.teletext.ITeletextPageSubCode"; + field public static final String ITKGS_INFO = "android.media.tv.extension.scan.ITkgsInfo"; + field public static final String ITKGS_INFO_LISTENER = "android.media.tv.extension.scan.ITkgsInfoListener"; + field public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = "android.media.tv.extension.signal.ITunerFrontendSignalInfoInterface"; + field public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.ITunerFrontendSignalInfoListener"; + field public static final String IVBI_RATING_INTERFACE = "android.media.tv.extension.rating.IVbiRatingInterface"; + field public static final String IVBI_RATING_LISTENER = "android.media.tv.extension.rating.IVbiRatingListener"; + field public static final String IVIDEO_SIGNAL_INFO = "android.media.tv.extension.signal.IVideoSignalInfo"; + field public static final String IVIDEO_SIGNAL_INFO_LISTENER = "android.media.tv.extension.signal.IVideoSignalInfoListener"; + field public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; // 0x2 + field public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; // 0x1 + field public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; // 0x3 + field public static final int REGISTER_SUCCESS = 0; // 0x0 + } + public abstract static class TvRecordingClient.RecordingCallback { method public void onEvent(String, String, android.os.Bundle); } @@ -12521,8 +12841,42 @@ package android.security.advancedprotection { } @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager { + method @NonNull public android.content.Intent createSupportIntent(@NonNull String, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List getAdvancedProtectionFeatures(); method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean); + field @FlaggedApi("android.security.aapm_api") public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; + field public static final String EXTRA_SUPPORT_DIALOG_FEATURE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; + field public static final String EXTRA_SUPPORT_DIALOG_TYPE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE"; + field public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = "android.security.advancedprotection.feature_disallow_2g"; + field public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = "android.security.advancedprotection.feature_disallow_install_unknown_sources"; + field public static final String FEATURE_ID_DISALLOW_USB = "android.security.advancedprotection.feature_disallow_usb"; + field public static final String FEATURE_ID_DISALLOW_WEP = "android.security.advancedprotection.feature_disallow_wep"; + field public static final String FEATURE_ID_ENABLE_MTE = "android.security.advancedprotection.feature_enable_mte"; + field public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = "android.security.advancedprotection.type_blocked_interaction"; + field public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = "android.security.advancedprotection.type_disabled_setting"; + } + +} + +package android.security.forensic { + + @FlaggedApi("android.security.afl_api") public class ForensicManager { + method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void addStateCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void disable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback); + method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void enable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback); + method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void removeStateCallback(@NonNull java.util.function.Consumer); + field public static final int ERROR_DATA_SOURCE_UNAVAILABLE = 4; // 0x4 + field public static final int ERROR_PERMISSION_DENIED = 1; // 0x1 + field public static final int ERROR_TRANSPORT_UNAVAILABLE = 3; // 0x3 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + field public static final int STATE_DISABLED = 1; // 0x1 + field public static final int STATE_ENABLED = 2; // 0x2 + field public static final int STATE_UNKNOWN = 0; // 0x0 + } + + public static interface ForensicManager.CommandCallback { + method public void onFailure(int); + method public void onSuccess(); } } @@ -14926,6 +15280,32 @@ package android.telephony { method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo(); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public final class CellularIdentifierDisclosure implements android.os.Parcelable { + method public int describeContents(); + method public int getCellularIdentifier(); + method public int getNasProtocolMessage(); + method @NonNull public String getPlmn(); + method public boolean isEmergency(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CELLULAR_IDENTIFIER_IMEI = 2; // 0x2 + field public static final int CELLULAR_IDENTIFIER_IMSI = 1; // 0x1 + field public static final int CELLULAR_IDENTIFIER_SUCI = 3; // 0x3 + field public static final int CELLULAR_IDENTIFIER_UNKNOWN = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST = 1; // 0x1 + field public static final int NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE = 6; // 0x6 + field public static final int NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST = 9; // 0x9 + field public static final int NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST = 10; // 0xa + field public static final int NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST = 8; // 0x8 + field public static final int NAS_PROTOCOL_MESSAGE_DETACH_REQUEST = 3; // 0x3 + field public static final int NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE = 2; // 0x2 + field public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; // 0xb + field public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; // 0x5 + field public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; // 0x7 + field public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; // 0x4 + field public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; // 0x0 + } + public final class DataFailCause { field @Deprecated public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be } @@ -15377,6 +15757,75 @@ package android.telephony { field public static final int USER_NOT_MEMBER_OF_CUG = 87; // 0x57 } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public final class SecurityAlgorithmUpdate implements android.os.Parcelable { + method public int describeContents(); + method public int getConnectionEvent(); + method public int getEncryption(); + method public int getIntegrity(); + method public boolean isUnprotectedEmergency(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11; // 0xb + field public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5; // 0x5 + field public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2; // 0x2 + field public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0; // 0x0 + field public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10; // 0xa + field public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4; // 0x4 + field public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3; // 0x3 + field public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1; // 0x1 + field public static final int CONNECTION_EVENT_VOLTE_RTP = 8; // 0x8 + field public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9; // 0x9 + field public static final int CONNECTION_EVENT_VOLTE_SIP = 6; // 0x6 + field public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7; // 0x7 + field public static final int CONNECTION_EVENT_VONR_RTP = 14; // 0xe + field public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15; // 0xf + field public static final int CONNECTION_EVENT_VONR_SIP = 12; // 0xc + field public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13; // 0xd + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int SECURITY_ALGORITHM_A50 = 0; // 0x0 + field public static final int SECURITY_ALGORITHM_A51 = 1; // 0x1 + field public static final int SECURITY_ALGORITHM_A52 = 2; // 0x2 + field public static final int SECURITY_ALGORITHM_A53 = 3; // 0x3 + field public static final int SECURITY_ALGORITHM_A54 = 4; // 0x4 + field public static final int SECURITY_ALGORITHM_AES_CBC = 71; // 0x47 + field public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73; // 0x49 + field public static final int SECURITY_ALGORITHM_AES_GCM = 69; // 0x45 + field public static final int SECURITY_ALGORITHM_AES_GMAC = 70; // 0x46 + field public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101; // 0x65 + field public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72; // 0x48 + field public static final int SECURITY_ALGORITHM_EEA0 = 41; // 0x29 + field public static final int SECURITY_ALGORITHM_EEA1 = 42; // 0x2a + field public static final int SECURITY_ALGORITHM_EEA2 = 43; // 0x2b + field public static final int SECURITY_ALGORITHM_EEA3 = 44; // 0x2c + field public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100; // 0x64 + field public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99; // 0x63 + field public static final int SECURITY_ALGORITHM_GEA0 = 14; // 0xe + field public static final int SECURITY_ALGORITHM_GEA1 = 15; // 0xf + field public static final int SECURITY_ALGORITHM_GEA2 = 16; // 0x10 + field public static final int SECURITY_ALGORITHM_GEA3 = 17; // 0x11 + field public static final int SECURITY_ALGORITHM_GEA4 = 18; // 0x12 + field public static final int SECURITY_ALGORITHM_GEA5 = 19; // 0x13 + field public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75; // 0x4b + field public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74; // 0x4a + field public static final int SECURITY_ALGORITHM_IMS_NULL = 67; // 0x43 + field public static final int SECURITY_ALGORITHM_NEA0 = 55; // 0x37 + field public static final int SECURITY_ALGORITHM_NEA1 = 56; // 0x38 + field public static final int SECURITY_ALGORITHM_NEA2 = 57; // 0x39 + field public static final int SECURITY_ALGORITHM_NEA3 = 58; // 0x3a + field public static final int SECURITY_ALGORITHM_ORYX = 124; // 0x7c + field public static final int SECURITY_ALGORITHM_OTHER = 114; // 0x72 + field public static final int SECURITY_ALGORITHM_RTP = 85; // 0x55 + field public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66; // 0x42 + field public static final int SECURITY_ALGORITHM_SIP_NULL = 68; // 0x44 + field public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87; // 0x57 + field public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88; // 0x58 + field public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89; // 0x59 + field public static final int SECURITY_ALGORITHM_SRTP_NULL = 86; // 0x56 + field public static final int SECURITY_ALGORITHM_UEA0 = 29; // 0x1d + field public static final int SECURITY_ALGORITHM_UEA1 = 30; // 0x1e + field public static final int SECURITY_ALGORITHM_UEA2 = 31; // 0x1f + field public static final int SECURITY_ALGORITHM_UNKNOWN = 113; // 0x71 + } + public class ServiceState implements android.os.Parcelable { method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int); method @NonNull public java.util.List getNetworkRegistrationInfoListForDomain(int); @@ -15601,6 +16050,7 @@ package android.telephony { field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4 field public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6 field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11 + field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; // 0x2f field @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5 field public static final int EVENT_DATA_ACTIVATION_STATE_CHANGED = 19; // 0x13 @@ -15625,6 +16075,7 @@ package android.telephony { field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED = 13; // 0xd field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_RADIO_POWER_STATE_CHANGED = 24; // 0x18 field @RequiresPermission(allOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_REGISTRATION_FAILURE = 31; // 0x1f + field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SECURITY_ALGORITHMS_CHANGED = 46; // 0x2e field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1 field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9 field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2 @@ -15643,6 +16094,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public static interface TelephonyCallback.CellularIdentifierDisclosedListener { + method public void onCellularIdentifierDisclosedChanged(@NonNull android.telephony.CellularIdentifierDisclosure); + } + public static interface TelephonyCallback.DataEnabledListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int); } @@ -15681,6 +16136,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int); } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public static interface TelephonyCallback.SecurityAlgorithmsListener { + method public void onSecurityAlgorithmsChanged(@NonNull android.telephony.SecurityAlgorithmUpdate); + } + @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set); } @@ -18784,6 +19243,10 @@ package android.webkit { method @Deprecated public void openFileChooser(android.webkit.ValueCallback, String, String); } + public abstract static class WebChromeClient.FileChooserParams { + field @FlaggedApi("android.webkit.file_system_access") public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L; // 0x15c127c5L + } + public abstract class WebHistoryItem implements java.lang.Cloneable { method @Deprecated public abstract int getId(); } diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 78577e2b40905f943e16ab8efaae051268e001f8..7c43891f13f28527f0abe7841497ea8cdaeab004 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -501,6 +501,52 @@ DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match + + +FlaggedApiLiteral: android.Manifest.permission#ACCESS_LAST_KNOWN_CELL_ID: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES). +FlaggedApiLiteral: android.Manifest.permission#BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_HEALTH_CONNECT_BACKUP_RESTORE_PERMISSION_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#BIND_VERIFICATION_AGENT: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_VERIFICATION_SERVICE). +FlaggedApiLiteral: android.Manifest.permission#EMBED_ANY_APP_IN_UNTRUSTED_MODE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.window.flags.Flags.FLAG_UNTRUSTED_EMBEDDING_ANY_APP_PERMISSION). +FlaggedApiLiteral: android.Manifest.permission#EXECUTE_APP_FUNCTIONS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER). +FlaggedApiLiteral: android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER). +FlaggedApiLiteral: android.Manifest.permission#MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW). +FlaggedApiLiteral: android.Manifest.permission#MANAGE_GLOBAL_SOUND_QUALITY_SERVICE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW). +FlaggedApiLiteral: android.Manifest.permission#QUARANTINE_APPS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_QUARANTINED_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#QUERY_DEVICE_STOLEN_STATE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#READ_BLOCKED_NUMBERS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES). +FlaggedApiLiteral: android.Manifest.permission#RECEIVE_SANDBOX_TRIGGER_AUDIO: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_VOICE_ACTIVATION_PERMISSION_APIS). +FlaggedApiLiteral: android.Manifest.permission#REGISTER_NSD_OFFLOAD_ENGINE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.net.platform.flags.Flags.FLAG_REGISTER_NSD_OFFLOAD_ENGINE). +FlaggedApiLiteral: android.Manifest.permission#RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_HEALTH_CONNECT_BACKUP_RESTORE_PERMISSION_ENABLED). +FlaggedApiLiteral: android.Manifest.permission#SET_ADVANCED_PROTECTION_MODE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.security.Flags.FLAG_AAPM_API). +FlaggedApiLiteral: android.Manifest.permission#SINGLE_USER_TIS_ACCESS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.media.tv.flags.Flags.FLAG_KIDS_MODE_TVDB_SHARING). +FlaggedApiLiteral: android.Manifest.permission#THREAD_NETWORK_PRIVILEGED: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM). +FlaggedApiLiteral: android.Manifest.permission#VERIFICATION_AGENT: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_VERIFICATION_SERVICE). +FlaggedApiLiteral: android.Manifest.permission#VIBRATE_VENDOR_EFFECTS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS). +FlaggedApiLiteral: android.Manifest.permission#WRITE_BLOCKED_NUMBERS: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES). +FlaggedApiLiteral: android.R.attr#backgroundPermission: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED). + + GenericException: android.app.prediction.AppPredictor#finalize(): Methods must not throw generic exceptions (`java.lang.Throwable`) GenericException: android.hardware.location.ContextHubClient#finalize(): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3ca55b932e196820747e3d74263217f744e14d32..44bcc2a737b9042cb44d295dd9b43b5894476f71 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -535,9 +535,13 @@ package android.app { public class WallpaperManager { method @Nullable public android.graphics.Bitmap getBitmap(); method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int); + method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List getBitmapCrops(@NonNull java.util.List, int, boolean); + method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List, @Nullable java.util.Map); method public boolean isLockscreenLiveWallpaperEnabled(); method @Nullable public android.graphics.Rect peekBitmapDimensions(); method @Nullable public android.graphics.Rect peekBitmapDimensions(int); + method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable android.graphics.Bitmap, @NonNull java.util.Map, boolean, int) throws java.io.IOException; + method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull java.util.Map, boolean, int) throws java.io.IOException; method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float); method public boolean shouldEnableWideColorGamut(); method public boolean wallpaperSupportsWcg(int); @@ -2061,6 +2065,29 @@ package android.media { method public boolean isAidlHal(); } + public final class MediaCodec { + method @FlaggedApi("android.media.codec.codec_availability") @NonNull public static java.util.List getGloballyAvailableResources(); + method @FlaggedApi("android.media.codec.codec_availability") @NonNull public java.util.List getRequiredResources(); + } + + public abstract static class MediaCodec.Callback { + method @FlaggedApi("android.media.codec.codec_availability") public void onRequiredResourcesChanged(@NonNull android.media.MediaCodec); + } + + @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.GlobalResourceInfo { + ctor public MediaCodec.GlobalResourceInfo(); + method public long getAvailable(); + method public long getCapacity(); + method @NonNull public String getName(); + } + + @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.InstanceResourceInfo { + ctor public MediaCodec.InstanceResourceInfo(); + method @NonNull public String getName(); + method public long getPerFrameCount(); + method public long getStaticCount(); + } + public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint { ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size); ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size); @@ -2291,7 +2318,7 @@ package android.net.wifi.sharedconnectivity.app { public class SharedConnectivityManager { method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String); - method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); + method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); method @Nullable public android.content.ServiceConnection getServiceConnection(); method public void setService(@Nullable android.os.IInterface); } @@ -3213,6 +3240,7 @@ package android.service.quickaccesswallet { method @Nullable public android.content.Intent createWalletIntent(); method @Nullable public android.content.Intent createWalletSettingsIntent(); method public void disconnect(); + method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void getGestureTargetActivityPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.GesturePendingIntentCallback); method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback); method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback); method public void getWalletPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletPendingIntentCallback); @@ -3224,6 +3252,10 @@ package android.service.quickaccesswallet { method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest); } + @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public static interface QuickAccessWalletClient.GesturePendingIntentCallback { + method @FlaggedApi("android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap") public void onGesturePendingIntentRetrieved(@Nullable android.app.PendingIntent); + } + public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback { method public void onWalletCardRetrievalError(@NonNull android.service.quickaccesswallet.GetWalletCardsError); method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse); @@ -3366,6 +3398,10 @@ package android.telephony { ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int); } + @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public final class CellularIdentifierDisclosure implements android.os.Parcelable { + ctor public CellularIdentifierDisclosure(int, int, @NonNull String, boolean); + } + public class MbmsDownloadSession implements java.lang.AutoCloseable { field public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override"; } @@ -3393,6 +3429,10 @@ package android.telephony { ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int); } + @FlaggedApi("com.android.internal.telephony.flags.security_algorithms_update_indications") public final class SecurityAlgorithmUpdate implements android.os.Parcelable { + ctor public SecurityAlgorithmUpdate(int, int, int, boolean); + } + public class ServiceState implements android.os.Parcelable { method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo); method public int getDataNetworkType(); diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index b4a3abc4ad22a4b8a50c667f5311328d935d275d..c2fac70d1f68d9fd23b7e33664c17af554ae7ffc 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1,4 +1,10 @@ // Baseline format: 1.0 +ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE: + Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE` +ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER: + Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER` + + BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer): Methods must not throw unchecked exceptions @@ -511,6 +517,12 @@ DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match +FlaggedApiLiteral: android.Manifest.permission#ACCESSIBILITY_MOTION_EVENT_OBSERVING: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.server.accessibility.Flags.FLAG_MOTION_EVENT_OBSERVING, however this flag doesn't seem to exist). +FlaggedApiLiteral: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver(): + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.wifi.flags.Flags.FLAG_SHARED_CONNECTIVITY_BROADCAST_RECEIVER_TEST_API, however this flag doesn't seem to exist). + + InvalidNullabilityOverride: android.window.WindowProviderService#getSystemService(String) parameter #0: Invalid nullability on parameter `name` in method `getSystemService`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. InvalidNullabilityOverride: android.window.WindowProviderService#onConfigurationChanged(android.content.res.Configuration) parameter #0: diff --git a/core/java/Android.bp b/core/java/Android.bp index cf5ebbaa37b4b34223fd04b488e7aa108da252a7..bc38294279a82367bfd915d44238c1c01d5eb2e6 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -232,6 +232,8 @@ aidl_interface { "android.hardware.power-aidl", ], srcs: [ + "android/os/CpuHeadroomParamsInternal.aidl", + "android/os/GpuHeadroomParamsInternal.aidl", "android/os/IHintManager.aidl", "android/os/IHintSession.aidl", ], diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS index bc8efa92c16fc13f49932747a686b2477fd8e630..4310d1a3a9db2e7005e9b19f92c70702cfdfd8d8 100644 --- a/core/java/android/adaptiveauth/OWNERS +++ b/core/java/android/adaptiveauth/OWNERS @@ -1 +1 @@ -include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS \ No newline at end of file +include /services/core/java/com/android/server/security/authenticationpolicy/OWNERS \ No newline at end of file diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3fccc17e1bf12741a0758ea475e2a9ce39319dfb..419eb7dac5f081dc90e7079461f48e79c611548a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -54,6 +54,7 @@ import android.app.VoiceInteractor.Request; import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; import android.app.compat.CompatChanges; +import android.app.jank.JankTracker; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -124,6 +125,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SuperNotCalledException; import android.view.ActionMode; +import android.view.Choreographer; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; @@ -175,6 +177,7 @@ import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; import com.android.internal.util.dump.DumpableContainerImpl; @@ -1145,6 +1148,9 @@ public class Activity extends ContextThemeWrapper }; + @Nullable + private JankTracker mJankTracker; + private static native String getDlWarning(); /** @@ -2245,6 +2251,10 @@ public class Activity extends ContextThemeWrapper // Notify autofill getAutofillClientController().onActivityPostResumed(); + if (android.app.jank.Flags.detailedAppJankMetricsApi()) { + startAppJankTracking(); + } + mCalled = true; } @@ -9264,6 +9274,9 @@ public class Activity extends ContextThemeWrapper dispatchActivityPrePaused(); mDoReportFullyDrawn = false; mFragments.dispatchPause(); + if (android.app.jank.Flags.detailedAppJankMetricsApi()) { + stopAppJankTracking(); + } mCalled = false; final long startTime = SystemClock.uptimeMillis(); onPause(); @@ -9946,4 +9959,49 @@ public class Activity extends ContextThemeWrapper mScreenCaptureCallbackHandler.unregisterScreenCaptureCallback(callback); } } + + /** + * Enabling jank tracking for this activity but only if certain conditions are met. The + * application must have an app category other than undefined and a visible view. + */ + private void startAppJankTracking() { + if (!android.app.jank.Flags.detailedAppJankMetricsLoggingEnabled()) { + return; + } + if (mApplication.getApplicationInfo().category == ApplicationInfo.CATEGORY_UNDEFINED) { + return; + } + if (getWindow() != null && getWindow().peekDecorView() != null) { + DecorView decorView = (DecorView) getWindow().peekDecorView(); + if (decorView.getVisibility() == View.VISIBLE) { + decorView.setAppJankStatsCallback(new DecorView.AppJankStatsCallback() { + @Override + public JankTracker getAppJankTracker() { + return mJankTracker; + } + }); + if (mJankTracker == null) { + // TODO b/377960907 use the Choreographer attached to the ViewRootImpl instead. + mJankTracker = new JankTracker(Choreographer.getInstance(), + decorView); + } + // TODO b/377674765 confirm this is the string we want logged. + mJankTracker.setActivityName(getComponentName().getClassName()); + mJankTracker.setAppUid(myUid()); + mJankTracker.enableAppJankTracking(); + } + } + } + + /** + * Call to disable jank tracking for this activity. + */ + private void stopAppJankTracking() { + if (!android.app.jank.Flags.detailedAppJankMetricsLoggingEnabled()) { + return; + } + if (mJankTracker != null) { + mJankTracker.disableAppJankTracking(); + } + } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b447897733e148d2ddc1a4081eede9368e9737d5..ab75069cc5d8b42d11015b6b1d9ab3f2b8f80616 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1016,6 +1016,12 @@ public class ActivityManager { */ public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6; + /** + * @hide + * Process is guaranteed cpu time (IE. it will not be frozen). + */ + public static final int PROCESS_CAPABILITY_CPU_TIME = 1 << 7; + /** * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}. * @@ -1028,7 +1034,8 @@ public class ActivityManager { | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_BFSL | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK - | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; + | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL + | PROCESS_CAPABILITY_CPU_TIME; /** * All implicit capabilities. This capability set is currently only used for processes under @@ -1053,6 +1060,7 @@ public class ActivityManager { pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); + pw.print((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-'); } /** @hide */ @@ -1065,6 +1073,7 @@ public class ActivityManager { sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); + sb.append((caps & PROCESS_CAPABILITY_CPU_TIME) != 0 ? 'T' : '-'); } /** @@ -5291,7 +5300,6 @@ public class ActivityManager { if (!exported) { /* RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid, here); */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 60b8f80d8f2dcc47cdd08c5fe62436169cf38781..cb7b1153988af4973746ad334ed95732ca74b475 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1961,12 +1961,8 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { - try { - PropertyInvalidatedCache.dumpCacheInfo(pfd, args); - BroadcastStickyCache.dump(pfd); - } finally { - IoUtils.closeQuietly(pfd); - } + PropertyInvalidatedCache.dumpCacheInfo(pfd, args); + IoUtils.closeQuietly(pfd); } private File getDatabasesDir(Context context) { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index fd70f4fc3f257fdde624e6273cb342bdb506065f..ce0ec602e612bb9a05561ddc7926f99b496f0a92 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1624,9 +1624,19 @@ public class AppOpsManager { /** @hide Access to read oxygen saturation. */ public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION; + /** @hide Access to write system preferences. */ + public static final int OP_WRITE_SYSTEM_PREFERENCES = + AppOpEnums.APP_OP_WRITE_SYSTEM_PREFERENCES; + + /** @hide Access to audio playback and control APIs. */ + public static final int OP_CONTROL_AUDIO = AppOpEnums.APP_OP_CONTROL_AUDIO; + + /** @hide Similar to {@link OP_CONTROL_AUDIO}, but doesn't require capabilities. */ + public static final int OP_CONTROL_AUDIO_PARTIAL = AppOpEnums.APP_OP_CONTROL_AUDIO_PARTIAL; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 153; + public static final int _NUM_OP = 156; /** * All app ops represented as strings. @@ -1783,6 +1793,9 @@ public class AppOpsManager { OPSTR_READ_SKIN_TEMPERATURE, OPSTR_RANGING, OPSTR_READ_OXYGEN_SATURATION, + OPSTR_WRITE_SYSTEM_PREFERENCES, + OPSTR_CONTROL_AUDIO, + OPSTR_CONTROL_AUDIO_PARTIAL, }) public @interface AppOpString {} @@ -2527,12 +2540,12 @@ public class AppOpsManager { /** @hide Access to read oxygen saturation. */ @SystemApi - @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED) + @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation"; /** @hide Access to read skin temperature. */ @SystemApi - @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED) + @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; /** @hide Access to ranging */ @@ -2540,6 +2553,15 @@ public class AppOpsManager { @FlaggedApi(Flags.FLAG_RANGING_PERMISSION_ENABLED) public static final String OPSTR_RANGING = "android:ranging"; + /** @hide Access to system preferences write services */ + public static final String OPSTR_WRITE_SYSTEM_PREFERENCES = "android:write_system_preferences"; + + /** @hide Access to audio playback and control APIs */ + public static final String OPSTR_CONTROL_AUDIO = "android:control_audio"; + + /** @hide Access to a audio playback and control APIs without capability requirements */ + public static final String OPSTR_CONTROL_AUDIO_PARTIAL = "android:control_audio_partial"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2616,8 +2638,8 @@ public class AppOpsManager { OP_POST_NOTIFICATION, // Health Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE, - Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE, - Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE, + Flags.replaceBodySensorPermissionEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE, + Flags.replaceBodySensorPermissionEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE, }; /** @@ -2656,6 +2678,7 @@ public class AppOpsManager { OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OP_WRITE_SYSTEM_PREFERENCES, }; @SuppressWarnings("FlaggedApi") @@ -3132,7 +3155,7 @@ public class AppOpsManager { .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE, "READ_SKIN_TEMPERATURE").setPermission( - Flags.platformSkinTemperatureEnabled() + Flags.replaceBodySensorPermissionEnabled() ? HealthPermissions.READ_SKIN_TEMPERATURE : null) .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING") @@ -3141,9 +3164,17 @@ public class AppOpsManager { .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION, "READ_OXYGEN_SATURATION").setPermission( - Flags.platformOxygenSaturationEnabled() + Flags.replaceBodySensorPermissionEnabled() ? HealthPermissions.READ_OXYGEN_SATURATION : null) .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_WRITE_SYSTEM_PREFERENCES, OPSTR_WRITE_SYSTEM_PREFERENCES, + "WRITE_SYSTEM_PREFERENCES").setPermission( + com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled() + ? Manifest.permission.WRITE_SYSTEM_PREFERENCES : null).build(), + new AppOpInfo.Builder(OP_CONTROL_AUDIO, OPSTR_CONTROL_AUDIO, + "CONTROL_AUDIO").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(), + new AppOpInfo.Builder(OP_CONTROL_AUDIO_PARTIAL, OPSTR_CONTROL_AUDIO_PARTIAL, + "CONTROL_AUDIO_PARTIAL").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index fb5a12b49921ac53b8a72e0147f280a1e7e64596..7e0a9b69b7bd16149232a696708e1251ef5d573a 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -16,6 +16,7 @@ package android.app; +import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; import static android.app.PropertyInvalidatedCache.createSystemCacheKey; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED; @@ -782,44 +783,25 @@ public class ApplicationPackageManager extends PackageManager { return hasSystemFeature(name, 0); } + /** + * The API and cache name for hasSystemFeature. + */ + private static final String HAS_SYSTEM_FEATURE_API = "has_system_feature"; + /** * Identifies a single hasSystemFeature query. */ - @Immutable - private static final class HasSystemFeatureQuery { - public final String name; - public final int version; - public HasSystemFeatureQuery(String n, int v) { - name = n; - version = v; - } - @Override - public String toString() { - return String.format("HasSystemFeatureQuery(name=\"%s\", version=%d)", - name, version); - } - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof HasSystemFeatureQuery) { - HasSystemFeatureQuery r = (HasSystemFeatureQuery) o; - return Objects.equals(name, r.name) && version == r.version; - } else { - return false; - } - } - @Override - public int hashCode() { - return Objects.hashCode(name) * 13 + version; - } - } + private record HasSystemFeatureQuery(String name, int version) {} // Make this cache relatively large. There are many system features and // none are ever invalidated. MPTS tests suggests that the cache should // hold at least 150 entries. private final static PropertyInvalidatedCache - mHasSystemFeatureCache = - new PropertyInvalidatedCache( - 256, createSystemCacheKey("has_system_feature")) { + mHasSystemFeatureCache = new PropertyInvalidatedCache<>( + new PropertyInvalidatedCache.Args(MODULE_SYSTEM) + .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false), + HAS_SYSTEM_FEATURE_API, null) { + @Override public Boolean recompute(HasSystemFeatureQuery query) { try { @@ -1835,7 +1817,6 @@ public class ApplicationPackageManager extends PackageManager { if (false) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId) + " from package " + packageName + ": app scale=" + r.getCompatibilityInfo().applicationScale diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java deleted file mode 100644 index ea8173191a3fb41d1118fa98215f36b0453daa15..0000000000000000000000000000000000000000 --- a/core/java/android/app/BroadcastStickyCache.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.app; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbManager; -import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.net.TetheringManager; -import android.net.nsd.NsdManager; -import android.net.wifi.WifiManager; -import android.net.wifi.p2p.WifiP2pManager; -import android.os.ParcelFileDescriptor; -import android.os.SystemProperties; -import android.os.UpdateLock; -import android.telephony.TelephonyManager; -import android.util.ArrayMap; -import android.util.IndentingPrintWriter; -import android.view.WindowManagerPolicyConstants; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FastPrintWriter; - -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.ArrayList; - -/** @hide */ -public class BroadcastStickyCache { - - private static final String[] CACHED_BROADCAST_ACTIONS = { - AudioManager.ACTION_HDMI_AUDIO_PLUG, - AudioManager.ACTION_HEADSET_PLUG, - AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED, - AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED, - AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, - AudioManager.RINGER_MODE_CHANGED_ACTION, - ConnectivityManager.CONNECTIVITY_ACTION, - Intent.ACTION_BATTERY_CHANGED, - Intent.ACTION_DEVICE_STORAGE_FULL, - Intent.ACTION_DEVICE_STORAGE_LOW, - Intent.ACTION_SIM_STATE_CHANGED, - NsdManager.ACTION_NSD_STATE_CHANGED, - TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, - TetheringManager.ACTION_TETHER_STATE_CHANGED, - UpdateLock.UPDATE_LOCK_CHANGED, - UsbManager.ACTION_USB_STATE, - WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED, - WifiManager.NETWORK_STATE_CHANGED_ACTION, - WifiManager.SUPPLICANT_STATE_CHANGED_ACTION, - WifiManager.WIFI_STATE_CHANGED_ACTION, - WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, - WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, - "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION - }; - - @GuardedBy("sCachedStickyBroadcasts") - private static final ArrayList sCachedStickyBroadcasts = - new ArrayList<>(); - - @GuardedBy("sCachedPropertyHandles") - private static final ArrayMap sCachedPropertyHandles = - new ArrayMap<>(); - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public static boolean useCache(@Nullable IntentFilter filter) { - if (!shouldCache(filter)) { - return false; - } - synchronized (sCachedStickyBroadcasts) { - final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); - if (cachedStickyBroadcast == null) { - return false; - } - final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */); - return version > 0 && cachedStickyBroadcast.version == version; - } - } - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) { - if (!shouldCache(filter)) { - return; - } - synchronized (sCachedStickyBroadcasts) { - CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); - if (cachedStickyBroadcast == null) { - final String key = getKey(filter.getAction(0)); - final SystemProperties.Handle handle = SystemProperties.find(key); - final long version = handle == null ? -1 : handle.getLong(-1 /* def */); - if (version == -1) { - return; - } - cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle); - sCachedStickyBroadcasts.add(cachedStickyBroadcast); - cachedStickyBroadcast.intent = intent; - cachedStickyBroadcast.version = version; - } else { - cachedStickyBroadcast.intent = intent; - cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle - .getLong(-1 /* def */); - } - } - } - - private static boolean shouldCache(@Nullable IntentFilter filter) { - if (!Flags.useStickyBcastCache()) { - return false; - } - if (filter == null || filter.safeCountActions() != 1) { - return false; - } - if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) { - return false; - } - return true; - } - - @VisibleForTesting - @NonNull - public static String getKey(@NonNull String action) { - return "cache_key.system_server.sticky_bcast." + action; - } - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - @Nullable - public static Intent getIntentUnchecked(@NonNull IntentFilter filter) { - synchronized (sCachedStickyBroadcasts) { - final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); - return cachedStickyBroadcast.intent; - } - } - - @GuardedBy("sCachedStickyBroadcasts") - @Nullable - private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) { - for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) { - final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i); - if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) { - return cachedStickyBroadcast; - } - } - return null; - } - - public static void incrementVersion(@NonNull String action) { - if (!shouldIncrementVersion(action)) { - return; - } - final String key = getKey(action); - synchronized (sCachedPropertyHandles) { - SystemProperties.Handle handle = sCachedPropertyHandles.get(key); - final long version; - if (handle == null) { - handle = SystemProperties.find(key); - if (handle != null) { - sCachedPropertyHandles.put(key, handle); - } - } - version = handle == null ? 0 : handle.getLong(0 /* def */); - SystemProperties.set(key, String.valueOf(version + 1)); - if (handle == null) { - sCachedPropertyHandles.put(key, SystemProperties.find(key)); - } - } - } - - public static void incrementVersionIfExists(@NonNull String action) { - if (!shouldIncrementVersion(action)) { - return; - } - final String key = getKey(action); - synchronized (sCachedPropertyHandles) { - final SystemProperties.Handle handle = sCachedPropertyHandles.get(key); - if (handle == null) { - return; - } - final long version = handle.getLong(0 /* def */); - SystemProperties.set(key, String.valueOf(version + 1)); - } - } - - private static boolean shouldIncrementVersion(@NonNull String action) { - if (!Flags.useStickyBcastCache()) { - return false; - } - if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) { - return false; - } - return true; - } - - @VisibleForTesting - public static void clearForTest() { - synchronized (sCachedStickyBroadcasts) { - sCachedStickyBroadcasts.clear(); - } - synchronized (sCachedPropertyHandles) { - sCachedPropertyHandles.clear(); - } - } - - public static void dump(@NonNull ParcelFileDescriptor pfd) { - if (!Flags.useStickyBcastCache()) { - return; - } - final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor())); - synchronized (sCachedStickyBroadcasts) { - dumpLocked(pw); - } - pw.flush(); - } - - @GuardedBy("sCachedStickyBroadcasts") - private static void dumpLocked(@NonNull PrintWriter pw) { - final IndentingPrintWriter ipw = new IndentingPrintWriter( - pw, " " /* singleIndent */, " " /* prefix */); - ipw.println("Cached sticky broadcasts:"); - ipw.increaseIndent(); - final int count = sCachedStickyBroadcasts.size(); - if (count == 0) { - ipw.println(""); - } else { - for (int i = 0; i < count; ++i) { - final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i); - ipw.print("Entry #"); ipw.print(i); ipw.println(":"); - ipw.increaseIndent(); - ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString()); - ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent); - ipw.print("version="); ipw.println(cachedStickyBroadcast.version); - ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle); - ipw.decreaseIndent(); - } - } - ipw.decreaseIndent(); - } - - private static final class CachedStickyBroadcast { - @NonNull public final IntentFilter filter; - @Nullable public Intent intent; - @IntRange(from = 0) public long version; - @NonNull public final SystemProperties.Handle propertyHandle; - - CachedStickyBroadcast(@NonNull IntentFilter filter, - @NonNull SystemProperties.Handle propertyHandle) { - this.filter = filter; - this.propertyHandle = propertyHandle; - } - } -} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 3ae60d71facdc37c10c265077c9647fbdf3299e6..cd56957ed5d17cb9a0a26a09b4096ef786cab593 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1922,19 +1922,10 @@ class ContextImpl extends Context { } } try { - final Intent intent; - if (receiver == null && BroadcastStickyCache.useCache(filter)) { - intent = BroadcastStickyCache.getIntentUnchecked(filter); - } else { - intent = ActivityManager.getService().registerReceiverWithFeature( - mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), - AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, - userId, - flags); - if (receiver == null) { - BroadcastStickyCache.add(filter, intent); - } - } + final Intent intent = ActivityManager.getService().registerReceiverWithFeature( + mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), + AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId, + flags); if (intent != null) { intent.setExtrasClassLoader(getClassLoader()); // TODO: determine at registration time if caller is diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java index b06fb9e2f284077f5a14b443246702e2fc6891c5..233dc75b810f09b859dfb7e8b1cc524a122c5046 100644 --- a/core/java/android/app/DisabledWallpaperManager.java +++ b/core/java/android/app/DisabledWallpaperManager.java @@ -176,6 +176,13 @@ final class DisabledWallpaperManager extends WallpaperManager { return unsupported(); } + @Override + @NonNull + public SparseArray getBitmapCrops(int which) { + unsupported(); + return new SparseArray<>(); + } + @Override public List getBitmapCrops(@NonNull Point bitmapSize, @NonNull List displaySizes, @Nullable Map cropHints) { @@ -358,8 +365,9 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override - public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray cropHints, - boolean allowBackup, @SetWallpaperFlags int which) throws IOException { + public int setStreamWithCrops(@NonNull InputStream bitmapData, + @NonNull SparseArray cropHints, boolean allowBackup, @SetWallpaperFlags int which + ) throws IOException { return unsupportedInt(); } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 9615015033483276bfc8cfe9ce20c6734381264d..7a329cd541a215dd3311b635c7cadfec27c78bed 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -112,8 +112,8 @@ interface IActivityClientController { oneway void requestMultiwindowFullscreen(in IBinder token, in int request, in IRemoteCallback callback); - oneway void startLockTaskModeByToken(in IBinder token); - oneway void stopLockTaskModeByToken(in IBinder token); + void startLockTaskModeByToken(in IBinder token); + void stopLockTaskModeByToken(in IBinder token); oneway void showLockTaskEscapeMessage(in IBinder token); void setTaskDescription(in IBinder token, in ActivityManager.TaskDescription values); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 34a3ad19b2701bd91615c12396ddb46056dd0b76..a8412fa666095c6ecbb2fa7e531925d628059eb1 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -358,7 +358,7 @@ interface IActivityManager { @UnsupportedAppUsage void resumeAppSwitches(); boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId, - int backupDestination); + int backupDestination, boolean useRestrictedMode); void backupAgentCreated(in String packageName, in IBinder agent, int userId); void unbindBackupAgent(in ApplicationInfo appInfo); int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 0654ac2f33ceb58fc01d6dcfb18b1d1209e8f962..9bb16ae7fa0211fe70f2c8552c81c172c8edb38f 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -124,7 +124,7 @@ interface INotificationManager boolean onlyHasDefaultChannel(String pkg, int uid); boolean areChannelsBypassingDnd(); ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid); - List getPackagesBypassingDnd(int userId, boolean includeConversationChannels); + ParceledListSlice getPackagesBypassingDnd(int userId); boolean isPackagePaused(String pkg); void deleteNotificationHistoryItem(String pkg, int uid, long postedTime); boolean isPermissionFixed(String pkg, int userId); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index f693e9ba11eca45afd504591cfcef32dbe3581da..6449ea1742a1abf856d37167fc75f4b9e81cbb68 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -96,6 +96,16 @@ interface IWallpaperManager { @SuppressWarnings(value={"untyped-collection"}) List getBitmapCrops(in List displaySizes, int which, boolean originalBitmap, int userId); + /** + * For a given user, if the wallpaper of the specified which is an ImageWallpaper, return + * a bundle which is a Map containing the custom cropHints that were sent to + * setBitmapWithCrops or setStreamWithCrops. These crops are relative to the original bitmap. + * If the wallpaper isn't an ImageWallpaper, return null. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)") + @SuppressWarnings(value={"untyped-collection"}) + Bundle getCurrentBitmapCrops(int which, int userId); + /** * Return how a bitmap of a given size would be cropped for a given list of display sizes when * set with the given suggested crops. diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 62820ad5a4d62a81a8bb4b1044bb0adeab2a6bb6..67f7bee4028e4ad2dca60b66dbc19e1b42c6d080 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -18,6 +18,7 @@ package android.app; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,7 +53,9 @@ import android.view.IOnKeyguardExitResult; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.IDeviceLockedStateListener; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.util.Preconditions; @@ -253,6 +256,26 @@ public class KeyguardManager { private final ArrayMap mKeyguardLockedStateListeners = new ArrayMap<>(); + private final IDeviceLockedStateListener mIDeviceLockedStateListener = + new IDeviceLockedStateListener.Stub() { + @Override + public void onDeviceLockedStateChanged(boolean isDeviceLocked) { + if (!Flags.deviceUnlockListener()) { + return; + } + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.forEach((listener, executor) -> { + executor.execute( + () -> listener.onDeviceLockedStateChanged(isDeviceLocked)); + }); + } + } + }; + + @GuardedBy("mDeviceLockedStateListeners") + private final ArrayMap + mDeviceLockedStateListeners = new ArrayMap<>(); + /** * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics * if enrolled) for the current user of the device. The caller is expected to launch this @@ -1370,4 +1393,77 @@ public class KeyguardManager { } } } + + + /** + * Listener for device locked state changes. + */ + @FunctionalInterface + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public interface DeviceLockedStateListener { + /** + * Callback function that executes when the device locked state changes. + */ + void onDeviceLockedStateChanged(boolean isDeviceLocked); + } + + + /** + * Registers a listener to execute when the device locked state changes. + * + * @param executor The {@link Executor} where the {@code listener} will be invoked + * @param listener The listener to add to receive device locked state changes. + * + * @see #isDeviceLocked() + * @see #removeDeviceLockedStateListener(DeviceLockedStateListener) + */ + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public void addDeviceLockedStateListener(@NonNull @CallbackExecutor Executor executor, + @NonNull DeviceLockedStateListener listener) { + if (!Flags.deviceUnlockListener()) { + return; + } + + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.put(listener, executor); + if (mDeviceLockedStateListeners.size() > 1) { + return; + } + try { + mTrustManager.registerDeviceLockedStateListener(mIDeviceLockedStateListener, + mContext.getDeviceId()); + } catch (RemoteException re) { + Log.d(TAG, "TrustManager service died", re); + } + } + } + + /** + * Unregisters a listener that executes when the device locked state changes. + * + * @param listener The listener to remove. + * + * @see #isDeviceLocked() + * @see #addDeviceLockedStateListener(Executor, DeviceLockedStateListener) + */ + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER) + public void removeDeviceLockedStateListener(@NonNull DeviceLockedStateListener listener) { + if (!Flags.deviceUnlockListener()) { + return; + } + + synchronized (mDeviceLockedStateListeners) { + mDeviceLockedStateListeners.remove(listener); + if (!mDeviceLockedStateListeners.isEmpty()) { + return; + } + try { + mTrustManager.unregisterDeviceLockedStateListener(mIDeviceLockedStateListener); + } catch (RemoteException re) { + Log.d(TAG, "TrustManager service died", re); + } + } + } } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index b8233bc498b8f8f5cf72862217e0c062be6b15b8..3d85ea6a1fca6c4ac196f5ec944a1c911278c176 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1660,7 +1660,6 @@ public final class LoadedApk { } RuntimeException ex = new IllegalArgumentException( "Originally unregistered here:"); - ex.fillInStackTrace(); rd.setUnregisterLocation(ex); holder.put(r, rd); } @@ -1860,7 +1859,6 @@ public final class LoadedApk { mInstrumentation = instrumentation; mRegistered = registered; mLocation = new IntentReceiverLeaked(null); - mLocation.fillInStackTrace(); } void validate(Context context, Handler activityThread) { @@ -2000,7 +1998,6 @@ public final class LoadedApk { } RuntimeException ex = new IllegalArgumentException( "Originally unbound here:"); - ex.fillInStackTrace(); sd.setUnbindLocation(ex); holder.put(c, sd); } @@ -2076,7 +2073,6 @@ public final class LoadedApk { mActivityThread = activityThread; mActivityExecutor = null; mLocation = new ServiceConnectionLeaked(null); - mLocation.fillInStackTrace(); mFlags = flags; } @@ -2088,7 +2084,6 @@ public final class LoadedApk { mActivityThread = null; mActivityExecutor = activityExecutor; mLocation = new ServiceConnectionLeaked(null); - mLocation.fillInStackTrace(); mFlags = flags; } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index e2de716d7e74cac10bcc8af55f24dca566e4786c..a70d4937b46c9f919af04f5957afcb76b7bfd1c2 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -83,7 +83,7 @@ public abstract class LoaderManager { * transactions while in this call, since it can happen after an * activity's state is saved. See {@link FragmentManager#beginTransaction() * FragmentManager.openTransaction()} for further discussion on this. - * + * *

This function is guaranteed to be called prior to the release of * the last data that was supplied for this Loader. At this point * you should remove all use of the old data (since it will be released @@ -127,7 +127,7 @@ public abstract class LoaderManager { */ public void onLoaderReset(Loader loader); } - + /** * Ensures a loader is initialized and active. If the loader doesn't * already exist, one is created and (if the activity/fragment is currently @@ -228,7 +228,7 @@ class LoaderManagerImpl extends LoaderManager { boolean mStarted; boolean mRetaining; boolean mRetainingStarted; - + boolean mCreatingLoader; private FragmentHostCallback mHost; @@ -249,13 +249,13 @@ class LoaderManagerImpl extends LoaderManager { boolean mListenerRegistered; LoaderInfo mPendingLoader; - + public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks callbacks) { mId = id; mArgs = args; mCallbacks = callbacks; } - + void start() { if (mRetaining && mRetainingStarted) { // Our owner is started, but we were being retained from a @@ -271,7 +271,7 @@ class LoaderManagerImpl extends LoaderManager { } mStarted = true; - + if (DEBUG) Log.v(TAG, " Starting: " + this); if (mLoader == null && mCallbacks != null) { mLoader = mCallbacks.onCreateLoader(mId, mArgs); @@ -291,7 +291,7 @@ class LoaderManagerImpl extends LoaderManager { mLoader.startLoading(); } } - + void retain() { if (DEBUG) Log.v(TAG, " Retaining: " + this); mRetaining = true; @@ -299,7 +299,7 @@ class LoaderManagerImpl extends LoaderManager { mStarted = false; mCallbacks = null; } - + void finishRetain() { if (mRetaining) { if (DEBUG) Log.v(TAG, " Finished Retaining: " + this); @@ -324,7 +324,7 @@ class LoaderManagerImpl extends LoaderManager { callOnLoadFinished(mLoader, mData); } } - + void reportStart() { if (mStarted) { if (mReportNextStart) { @@ -430,7 +430,7 @@ class LoaderManagerImpl extends LoaderManager { @Override public void onLoadComplete(Loader loader, Object data) { if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); - + if (mDestroyed) { if (DEBUG) Log.v(TAG, " Ignoring load complete -- destroyed"); return; @@ -442,7 +442,7 @@ class LoaderManagerImpl extends LoaderManager { if (DEBUG) Log.v(TAG, " Ignoring load complete -- not active"); return; } - + LoaderInfo pending = mPendingLoader; if (pending != null) { // There is a new request pending and we were just @@ -455,7 +455,7 @@ class LoaderManagerImpl extends LoaderManager { installLoader(pending); return; } - + // Notify of the new data so the app can switch out the old data before // we try to destroy it. if (mData != data || !mHaveData) { @@ -503,7 +503,7 @@ class LoaderManagerImpl extends LoaderManager { mDeliveredData = true; } } - + @Override public String toString() { StringBuilder sb = new StringBuilder(64); @@ -543,13 +543,13 @@ class LoaderManagerImpl extends LoaderManager { } } } - + LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) { mWho = who; mHost = host; mStarted = started; } - + void updateHostController(FragmentHostCallback host) { mHost = host; } @@ -557,7 +557,7 @@ class LoaderManagerImpl extends LoaderManager { public FragmentHostCallback getFragmentHostCallback() { return mHost; } - + private LoaderInfo createLoader(int id, Bundle args, LoaderManager.LoaderCallbacks callback) { LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks)callback); @@ -565,7 +565,7 @@ class LoaderManagerImpl extends LoaderManager { info.mLoader = (Loader)loader; return info; } - + private LoaderInfo createAndInstallLoader(int id, Bundle args, LoaderManager.LoaderCallbacks callback) { try { @@ -577,7 +577,7 @@ class LoaderManagerImpl extends LoaderManager { mCreatingLoader = false; } } - + void installLoader(LoaderInfo info) { mLoaders.put(info.mId, info); if (mStarted) { @@ -587,23 +587,23 @@ class LoaderManagerImpl extends LoaderManager { info.start(); } } - + /** * Call to initialize a particular ID with a Loader. If this ID already * has a Loader associated with it, it is left unchanged and any previous * callbacks replaced with the newly provided ones. If there is not currently * a Loader for the ID, a new one is created and started. - * + * *

This function should generally be used when a component is initializing, * to ensure that a Loader it relies on is created. This allows it to re-use * an existing Loader's data if there already is one, so that for example * when an {@link Activity} is re-created after a configuration change it * does not need to re-create its loaders. - * + * *

Note that in the case where an existing Loader is re-used, the * args given here will be ignored because you will * continue using the previous Loader. - * + * * @param id A unique (to this LoaderManager instance) identifier under * which to manage the new Loader. * @param args Optional arguments that will be propagated to @@ -617,9 +617,9 @@ class LoaderManagerImpl extends LoaderManager { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } - + LoaderInfo info = mLoaders.get(id); - + if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); if (info == null) { @@ -630,30 +630,30 @@ class LoaderManagerImpl extends LoaderManager { if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); info.mCallbacks = (LoaderManager.LoaderCallbacks)callback; } - + if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } - + return (Loader)info.mLoader; } - + /** * Call to re-create the Loader associated with a particular ID. If there * is currently a Loader associated with this ID, it will be * canceled/stopped/destroyed as appropriate. A new Loader with the given * arguments will be created and its data delivered to you once available. - * + * *

This function does some throttling of Loaders. If too many Loaders * have been created for the given ID but not yet generated their data, * new calls to this function will create and return a new Loader but not * actually start it until some previous loaders have completed. - * + * *

After calling this function, any previous Loaders associated with * this ID will be considered invalid, and you will receive no further * data updates from them. - * + * * @param id A unique (to this LoaderManager instance) identifier under * which to manage the new Loader. * @param args Optional arguments that will be propagated to @@ -667,7 +667,7 @@ class LoaderManagerImpl extends LoaderManager { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } - + LoaderInfo info = mLoaders.get(id); if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args); if (info != null) { @@ -706,7 +706,7 @@ class LoaderManagerImpl extends LoaderManager { info.mPendingLoader = null; } if (DEBUG) Log.v(TAG, " Enqueuing as new pending loader"); - info.mPendingLoader = createLoader(id, args, + info.mPendingLoader = createLoader(id, args, (LoaderManager.LoaderCallbacks)callback); return (Loader)info.mPendingLoader.mLoader; } @@ -719,11 +719,11 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.put(id, info); } } - + info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks)callback); return (Loader)info.mLoader; } - + /** * Rip down, tear apart, shred to pieces a current Loader ID. After returning * from this function, any Loader objects associated with this ID are @@ -735,7 +735,7 @@ class LoaderManagerImpl extends LoaderManager { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } - + if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id); int idx = mLoaders.indexOfKey(id); if (idx >= 0) { @@ -763,7 +763,7 @@ class LoaderManagerImpl extends LoaderManager { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } - + LoaderInfo loaderInfo = mLoaders.get(id); if (loaderInfo != null) { if (loaderInfo.mPendingLoader != null) { @@ -773,16 +773,15 @@ class LoaderManagerImpl extends LoaderManager { } return null; } - + void doStart() { if (DEBUG) Log.v(TAG, "Starting in " + this); if (mStarted) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "Called doStart when already started: " + this, e); return; } - + mStarted = true; // Call out to sub classes so they can start their loaders @@ -791,12 +790,11 @@ class LoaderManagerImpl extends LoaderManager { mLoaders.valueAt(i).start(); } } - + void doStop() { if (DEBUG) Log.v(TAG, "Stopping in " + this); if (!mStarted) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "Called doStop when not started: " + this, e); return; } @@ -806,12 +804,11 @@ class LoaderManagerImpl extends LoaderManager { } mStarted = false; } - + void doRetain() { if (DEBUG) Log.v(TAG, "Retaining in " + this); if (!mStarted) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "Called doRetain when not started: " + this, e); return; } @@ -822,7 +819,7 @@ class LoaderManagerImpl extends LoaderManager { mLoaders.valueAt(i).retain(); } } - + void finishRetain() { if (mRetaining) { if (DEBUG) Log.v(TAG, "Finished Retaining in " + this); @@ -833,7 +830,7 @@ class LoaderManagerImpl extends LoaderManager { } } } - + void doReportNextStart() { for (int i = mLoaders.size()-1; i >= 0; i--) { mLoaders.valueAt(i).mReportNextStart = true; @@ -854,7 +851,7 @@ class LoaderManagerImpl extends LoaderManager { } mLoaders.clear(); } - + if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this); for (int i = mInactiveLoaders.size()-1; i >= 0; i--) { mInactiveLoaders.valueAt(i).destroy(); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0381ee0e25ac7dd1d3c72636250624284b215283..1c23ec7995992b6a442982eeca3a72a6c85b1b2b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -811,11 +811,20 @@ public class Notification implements Parcelable } private static boolean isStandardLayout(int layoutId) { + // TODO: b/359128724 - Add to static list when inlining the flag. if (Flags.apiRichOngoing()) { if (layoutId == R.layout.notification_template_material_progress) { return true; } } + // TODO: b/378660052 - Add to static list when inlining the flag. + if (Flags.notificationsRedesignTemplates()) { + switch(layoutId) { + case R.layout.notification_2025_template_collapsed_base: + case R.layout.notification_2025_template_header: + return true; + } + } return STANDARD_LAYOUTS.contains(layoutId); } @@ -5003,7 +5012,7 @@ public class Notification implements Parcelable /** * Sets a very short string summarizing the most critical information contained in the - * notification. Suggested max length is 5 characters, and there is no guarantee how much or + * notification. Suggested max length is 7 characters, and there is no guarantee how much or * how little of this text will be shown. */ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) @@ -6718,7 +6727,7 @@ public class Notification implements Parcelable // Headers on their own are never colorized p.disallowColorization(); RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), - R.layout.notification_template_header); + getHeaderLayoutResource()); resetNotificationHeader(header); bindNotificationHeader(header, p); return header; @@ -7478,9 +7487,21 @@ public class Notification implements Parcelable return clone; } + private int getHeaderLayoutResource() { + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_header; + } else { + return R.layout.notification_template_header; + } + } + @UnsupportedAppUsage private int getBaseLayoutResource() { - return R.layout.notification_template_material_base; + if (Flags.notificationsRedesignTemplates()) { + return R.layout.notification_2025_template_collapsed_base; + } else { + return R.layout.notification_template_material_base; + } } private int getHeadsUpBaseLayoutResource() { @@ -9499,7 +9520,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.icon, View.GONE); contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE); contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE); - contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true); contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon); } else if (mIsGroupConversation) { contentView.setViewVisibility(R.id.icon, View.GONE); diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 1dc774285a327bd11cf4d11d7b8162391c85062a..e218418336c54a559801f902d49edd0a1e4dca4a 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -314,6 +314,14 @@ public class PropertyInvalidatedCache { @GuardedBy("mLock") private long mMisses = 0; + // This counter tracks the number of times {@link #recompute} returned a null value. Null + // results are cached, or not, depending on instantiation arguments. Caching nulls when they + // should not be cached is a functional error. Failing to cache nulls that can be cached is a + // performance error. A non-zero value here means the cache should be examined to be sure + // that nulls are correctly cached, or not. + @GuardedBy("mLock") + private long mNulls = 0; + @GuardedBy("mLock") private long[] mSkips = new long[MAX_RESERVED_NONCE + 1]; @@ -373,6 +381,11 @@ public class PropertyInvalidatedCache { */ private final String mCacheName; + /** + * True if nulls are valid returns from recompute(). + */ + private final boolean mCacheNullResults; + /** * The function that computes a Result, given a Query. This function is called on a * cache miss. @@ -508,6 +521,19 @@ public class PropertyInvalidatedCache { } } + /** + * Return true if the entry is in the cache. + */ + boolean containsKey(Query query) { + final int uid = callerUid(); + var map = mCache.get(uid); + if (map != null) { + return map.containsKey(query); + } else { + return false; + } + } + /** * Remove an entry from the cache. */ @@ -1101,7 +1127,7 @@ public class PropertyInvalidatedCache { * @hide */ public static record Args(@NonNull String mModule, @Nullable String mApi, - int mMaxEntries, boolean mIsolateUids, boolean mTestMode) { + int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) { // Validation: the module must be one of the known module strings and the maxEntries must // be positive. @@ -1119,24 +1145,29 @@ public class PropertyInvalidatedCache { null, // api 32, // maxEntries true, // isolateUids - false // testMode + false, // testMode + true // allowNulls ); } public Args api(@NonNull String api) { - return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode); + return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode, mCacheNulls); } public Args maxEntries(int val) { - return new Args(mModule, mApi, val, mIsolateUids, mTestMode); + return new Args(mModule, mApi, val, mIsolateUids, mTestMode, mCacheNulls); } public Args isolateUids(boolean val) { - return new Args(mModule, mApi, mMaxEntries, val, mTestMode); + return new Args(mModule, mApi, mMaxEntries, val, mTestMode, mCacheNulls); } public Args testMode(boolean val) { - return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val); + return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val, mCacheNulls); + } + + public Args cacheNulls(boolean val) { + return new Args(mModule, mApi, mMaxEntries, mIsolateUids, mTestMode, val); } } @@ -1153,6 +1184,7 @@ public class PropertyInvalidatedCache { @Nullable QueryHandler computer) { mPropertyName = createPropertyName(args.mModule, args.mApi); mCacheName = cacheName; + mCacheNullResults = args.mCacheNulls && Flags.picCacheNulls(); mNonce = getNonceHandler(mPropertyName); mMaxEntries = args.mMaxEntries; mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode); @@ -1491,12 +1523,24 @@ public class PropertyInvalidatedCache { } return recompute(query); } + + final boolean cacheHit; final Result cachedResult; synchronized (mLock) { if (currentNonce == mLastSeenNonce) { cachedResult = mCache.get(query); - - if (cachedResult != null) mHits++; + if (cachedResult == null) { + if (mCacheNullResults) { + cacheHit = mCache.containsKey(query); + } else { + cacheHit = false; + } + } else { + cacheHit = true; + } + if (cacheHit) { + mHits++; + } } else { if (DEBUG) { Log.d(TAG, formatSimple( @@ -1506,16 +1550,18 @@ public class PropertyInvalidatedCache { } clear(); mLastSeenNonce = currentNonce; + cacheHit = false; cachedResult = null; } } + // Cache hit --- but we're not quite done yet. A value in the cache might need to // be augmented in a "refresh" operation. The refresh operation can combine the // old and the new nonce values. In order to make sure the new parts of the value // are consistent with the old, possibly-reused parts, we check the property value // again after the refresh and do the whole fetch again if the property invalidated // us while we were refreshing. - if (cachedResult != null) { + if (cacheHit) { final Result refreshedResult = refresh(cachedResult, query); if (refreshedResult != cachedResult) { if (DEBUG) { @@ -1550,6 +1596,7 @@ public class PropertyInvalidatedCache { } return maybeCheckConsistency(query, cachedResult); } + // Cache miss: make the value from scratch. if (DEBUG) { Log.d(TAG, "cache miss for " + cacheName() + " " + queryToString(query)); @@ -1558,8 +1605,13 @@ public class PropertyInvalidatedCache { synchronized (mLock) { // If someone else invalidated the cache while we did the recomputation, don't // update the cache with a potentially stale result. - if (mLastSeenNonce == currentNonce && result != null) { - mCache.put(query, result); + if (mLastSeenNonce == currentNonce) { + if (result != null || mCacheNullResults) { + mCache.put(query, result); + } + if (result == null) { + mNulls++; + } } mMisses++; } @@ -1947,10 +1999,12 @@ public class PropertyInvalidatedCache { } // Return true if this cache has had any activity. If the hits, misses, and skips are all - // zero then the client never tried to use the cache. - private boolean isActive() { + // zero then the client never tried to use the cache. If invalidations and corks are also + // zero then the server never tried to use the cache. + private boolean isActive(NonceHandler.Stats stats) { synchronized (mLock) { - return mHits + mMisses + getSkipsLocked() > 0; + return mHits + mMisses + getSkipsLocked() + + stats.invalidated + stats.corkedInvalidates > 0; } } @@ -1968,15 +2022,15 @@ public class PropertyInvalidatedCache { NonceHandler.Stats stats = mNonce.getStats(); synchronized (mLock) { - if (brief && !isActive()) { + if (brief && !isActive(stats)) { return; } pw.println(formatSimple(" Cache Name: %s", cacheName())); pw.println(formatSimple(" Property: %s", mPropertyName)); pw.println(formatSimple( - " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d", - mHits, mMisses, getSkipsLocked(), mClears, mCache.size())); + " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Nulls: %d", + mHits, mMisses, getSkipsLocked(), mClears, mNulls)); // Print all the skip reasons. pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]); @@ -1986,7 +2040,7 @@ public class PropertyInvalidatedCache { pw.println(); pw.println(formatSimple( - " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d", + " Nonce: 0x%016x, Invalidates: %d, Corked: %d", mLastSeenNonce, stats.invalidated, stats.corkedInvalidates)); pw.println(formatSimple( " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index a458b4e45796732e12556514147add30d0c1f9b4..087e246e8841f636f39c09c38b887dc36f2ff126 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -174,22 +174,54 @@ public class ResourcesManager { } /** - * Apply the registered library paths to the passed impl object - * @return the hash code for the current version of the registered paths + * Apply the registered library paths to the passed AssetManager. If may create a new + * AssetManager if any changes are needed and it isn't allowed to reuse the old one. + * + * @return new AssetManager and the hash code for the current version of the registered paths */ - public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { + public @NonNull Pair updateResourceImplAssetsWithRegisteredLibs( + @NonNull AssetManager assets, boolean reuseAssets) { if (!Flags.registerResourcePaths()) { - return 0; + return new Pair<>(assets, 0); } - final var collector = new PathCollector(null); - final int size = mSharedLibAssetsMap.size(); - for (int i = 0; i < size; i++) { - final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); - collector.appendKey(libraryKey); + final int size; + final PathCollector collector; + + synchronized (mLock) { + size = mSharedLibAssetsMap.size(); + if (assets == AssetManager.getSystem()) { + return new Pair<>(assets, size); + } + collector = new PathCollector(resourcesKeyFromAssets(assets)); + for (int i = 0; i < size; i++) { + final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); + collector.appendKey(libraryKey); + } + } + if (collector.isSameAsOriginal()) { + return new Pair<>(assets, size); + } + if (reuseAssets) { + assets.addPresetApkKeys(extractApkKeys(collector.collectedKey())); + return new Pair<>(assets, size); + } + final var newAssetsBuilder = new AssetManager.Builder(); + for (final var asset : assets.getApkAssets()) { + if (!asset.isForLoader()) { + newAssetsBuilder.addApkAssets(asset); + } } - impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); - return size; + for (final var key : extractApkKeys(collector.collectedKey())) { + try { + final var asset = loadApkAssets(key); + newAssetsBuilder.addApkAssets(asset); + } catch (IOException e) { + Log.e(TAG, "Couldn't load assets for key " + key, e); + } + } + assets.getLoaders().forEach(newAssetsBuilder::addLoader); + return new Pair<>(newAssetsBuilder.build(), size); } public static class ApkKey { @@ -624,6 +656,23 @@ public class ResourcesManager { return apkKeys; } + private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) { + final var libs = new ArrayList(); + final var overlays = new ArrayList(); + for (final ApkAssets asset : assets.getApkAssets()) { + if (asset.isSystem() || asset.isForLoader()) { + continue; + } + if (asset.isOverlay()) { + overlays.add(asset.getAssetPath()); + } else if (asset.isSharedLib()) { + libs.add(asset.getAssetPath()); + } + } + return new ResourcesKey(null, null, overlays.toArray(new String[0]), + libs.toArray(new String[0]), 0, null, null); + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -752,7 +801,7 @@ public class ResourcesManager { final Configuration config = generateConfig(key); final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); - final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); + final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true); if (DEBUG) { Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); @@ -1137,7 +1186,6 @@ public class ResourcesManager { synchronized (mLock) { if (DEBUG) { Throwable here = new Throwable(); - here.fillInStackTrace(); Slog.w(TAG, "!! Create resources for key=" + key, here); } @@ -1158,7 +1206,6 @@ public class ResourcesManager { synchronized (mLock) { if (DEBUG) { Throwable here = new Throwable(); - here.fillInStackTrace(); Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); } @@ -1302,7 +1349,6 @@ public class ResourcesManager { if (DEBUG) { Throwable here = new Throwable(); - here.fillInStackTrace(); Slog.d(TAG, "updating resources override for activity=" + activityToken + " from oldConfig=" + Configuration.resourceQualifierString(oldConfig) @@ -1835,31 +1881,32 @@ public class ResourcesManager { for (int i = 0; i < resourcesCount; i++) { final WeakReference ref = mAllResourceReferences.get(i); final Resources r = ref != null ? ref.get() : null; - if (r != null) { - final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); - if (key != null) { - final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); - if (impl == null) { - throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); - } - r.setImpl(impl); - } else { - // ResourcesKey is null which means the ResourcesImpl could belong to a - // Resources created by application through Resources constructor and was not - // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to - // have shared library asset paths appended if there are any. - if (r.getImpl() != null) { - final ResourcesImpl oldImpl = r.getImpl(); - final AssetManager oldAssets = oldImpl.getAssets(); - // ResourcesImpl constructor will help to append shared library asset paths. - if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) { - final ResourcesImpl newImpl = new ResourcesImpl(oldAssets, - oldImpl.getMetrics(), oldImpl.getConfiguration(), - oldImpl.getDisplayAdjustments()); + if (r == null) { + continue; + } + final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); + if (key != null) { + final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); + if (impl == null) { + throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); + } + r.setImpl(impl); + } else { + // ResourcesKey is null which means the ResourcesImpl could belong to a + // Resources created by application through Resources constructor and was not + // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to + // have shared library asset paths appended if there are any. + final ResourcesImpl oldImpl = r.getImpl(); + if (oldImpl != null) { + final AssetManager oldAssets = oldImpl.getAssets(); + // ResourcesImpl constructor will help to append shared library asset paths. + if (oldAssets != AssetManager.getSystem()) { + if (oldAssets.isUpToDate()) { + final ResourcesImpl newImpl = new ResourcesImpl(oldImpl); r.setImpl(newImpl); } else { - Slog.w(TAG, "Skip appending shared library asset paths for the " - + "Resource as its assets are not up to date."); + Slog.w(TAG, "Skip appending shared library asset paths for " + + "the Resources as its assets are not up to date."); } } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e451116081fa9604c1ffee4745e08fd33aaa259b..53a7dad76788063d05a1eaf9ff0db4cfa7d4a2a2 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -17,6 +17,7 @@ package android.app; import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; +import static android.provider.flags.Flags.stageFlagsForBuild; import static android.server.Flags.removeGameManagerServiceFromWear; import android.accounts.AccountManager; @@ -163,6 +164,7 @@ import android.media.tv.tunerresourcemanager.ITunerResourceManager; import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.nearby.NearbyFrameworkInitializer; import android.net.ConnectivityFrameworkInitializer; +import android.net.ConnectivityFrameworkInitializerBaklava; import android.net.ConnectivityFrameworkInitializerTiramisu; import android.net.INetworkPolicyManager; import android.net.IPacProxyManager; @@ -173,7 +175,6 @@ import android.net.NetworkWatchlistManager; import android.net.PacProxyManager; import android.net.TetheringManager; import android.net.VpnManager; -import android.net.vcn.VcnFrameworkInitializer; import android.net.wifi.WifiFrameworkInitializer; import android.net.wifi.nl80211.WifiNl80211Manager; import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager; @@ -190,6 +191,7 @@ import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.IDumpstate; import android.os.IHardwarePropertiesManager; +import android.os.IHintManager; import android.os.IPowerManager; import android.os.IPowerStatsService; import android.os.IRecoverySystem; @@ -215,6 +217,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.os.VibratorManager; +import android.os.flagging.ConfigInfrastructureFrameworkInitializer; import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; @@ -238,6 +241,8 @@ import android.security.advancedprotection.AdvancedProtectionManager; import android.security.advancedprotection.IAdvancedProtectionService; import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.IAttestationVerificationManagerService; +import android.security.forensic.ForensicManager; +import android.security.forensic.IForensicService; import android.security.keystore.KeyStoreManager; import android.service.oemlock.IOemLockService; import android.service.oemlock.OemLockManager; @@ -1195,8 +1200,10 @@ public final class SystemServiceRegistry { public SystemHealthManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder batteryStats = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME); IBinder powerStats = ServiceManager.getService(Context.POWER_STATS_SERVICE); + IBinder perfHint = ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE); return new SystemHealthManager(IBatteryStats.Stub.asInterface(batteryStats), - IPowerStatsService.Stub.asInterface(powerStats)); + IPowerStatsService.Stub.asInterface(powerStats), + IHintManager.Stub.asInterface(perfHint)); }}); registerService(Context.CONTEXTHUB_SERVICE, ContextHubManager.class, @@ -1790,6 +1797,18 @@ public final class SystemServiceRegistry { } }); + registerService(Context.FORENSIC_SERVICE, ForensicManager.class, + new CachedServiceFetcher() { + @Override + public ForensicManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.FORENSIC_SERVICE); + IForensicService service = IForensicService.Stub.asInterface(b); + return new ForensicManager(service); + } + }); + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline @@ -1818,7 +1837,11 @@ public final class SystemServiceRegistry { OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers(); DeviceLockFrameworkInitializer.registerServiceWrappers(); VirtualizationFrameworkInitializer.registerServiceWrappers(); - VcnFrameworkInitializer.registerServiceWrappers(); + ConnectivityFrameworkInitializerBaklava.registerServiceWrappers(); + + if (stageFlagsForBuild()) { + ConfigInfrastructureFrameworkInitializer.registerServiceWrappers(); + } if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) { ProviderFrameworkInitializer.registerServiceWrappers(); diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 637187e01160f3bcd6611915ad3db332a28f7fdb..5ed1f4e355335d2ce6c43c521832c8b5524b3042 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -177,10 +177,6 @@ { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsAppOpsTestCases" - }, - { - "file_patterns": ["(/|^)BroadcastStickyCache.java"], - "name": "BroadcastUnitTests" } ] } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 479f3df9affb9a8875d7712eeca3c614f3b66fd9..abb2dd465576baaa1ef61830c39cade1aa45e50b 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -19,9 +19,10 @@ package android.app; import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT; +import static android.app.Flags.FLAG_CUSTOMIZATION_PACKS_APIS; +import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; -import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; import static com.android.window.flags.Flags.FLAG_MULTI_CROP; import static com.android.window.flags.Flags.multiCrop; @@ -342,24 +343,32 @@ public class WallpaperManager { * Portrait orientation of most screens * @hide */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi public static final int ORIENTATION_PORTRAIT = 0; /** * Landscape orientation of most screens * @hide */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi public static final int ORIENTATION_LANDSCAPE = 1; /** * Portrait orientation with similar width and height (e.g. the inner screen of a foldable) * @hide */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi public static final int ORIENTATION_SQUARE_PORTRAIT = 2; /** * Landscape orientation with similar width and height (e.g. the inner screen of a foldable) * @hide */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi public static final int ORIENTATION_SQUARE_LANDSCAPE = 3; /** @@ -368,7 +377,9 @@ public class WallpaperManager { * @return the corresponding {@link ScreenOrientation}. * @hide */ - public static @ScreenOrientation int getOrientation(Point screenSize) { + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi + public static @ScreenOrientation int getOrientation(@NonNull Point screenSize) { float ratio = ((float) screenSize.x) / screenSize.y; // ratios between 3/4 and 4/3 are considered square return ratio >= 4 / 3f ? ORIENTATION_LANDSCAPE @@ -1623,14 +1634,15 @@ public class WallpaperManager { * If false, return areas relative to the cropped bitmap. * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds * to what is displayed. The Rect may have a larger width/height ratio than the screen - * due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper. - * Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a + * due to parallax. Return an empty list if the wallpaper is not an ImageWallpaper. + * Also return an empty list when called with which={@link #FLAG_LOCK} if there is a * shared home + lock wallpaper. * @hide */ @FlaggedApi(FLAG_MULTI_CROP) + @TestApi @RequiresPermission(READ_WALLPAPER_INTERNAL) - @Nullable + @NonNull public List getBitmapCrops(@NonNull List displaySizes, @SetWallpaperFlags int which, boolean originalBitmap) { checkExactlyOneWallpaperFlagSet(which); @@ -1652,6 +1664,52 @@ public class WallpaperManager { } } + /** + * For the current user, if the wallpaper of the specified destination is an ImageWallpaper, + * return the custom crops of the wallpaper, that have been provided for example via + * {@link #setStreamWithCrops}. These crops are relative to the original bitmap. + *

+ * This method helps apps that change wallpapers provide an undo option. Calling + * {@link #setStreamWithCrops(InputStream, SparseArray, boolean, int)} with this SparseArray and + * the current original bitmap file, that can be obtained with {@link #getWallpaperFile(int, + * boolean)} with {@code getCropped=false}, will exactly lead to the current wallpaper state. + * + * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. + * @return A map from {{@link #ORIENTATION_PORTRAIT}, {@link #ORIENTATION_LANDSCAPE}, + * {@link #ORIENTATION_SQUARE_PORTRAIT}, {{@link #ORIENTATION_SQUARE_LANDSCAPE}}} to + * Rect, representing the custom cropHints. The map can be empty and will only contains + * entries for screen orientations for which a custom crop was provided. If no custom + * crop is provided for an orientation, the system will infer the crop based on the + * custom crops of the other orientations; or center-align the full image if no custom + * crops are provided at all. + *

+ * Return an empty map if the wallpaper is not an ImageWallpaper. Also return + * an empty map when called with which={@link #FLAG_LOCK} if there is a shared + * home + lock wallpaper. + * + * @hide + */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi + @RequiresPermission(READ_WALLPAPER_INTERNAL) + @NonNull + public SparseArray getBitmapCrops(@SetWallpaperFlags int which) { + checkExactlyOneWallpaperFlagSet(which); + try { + Bundle bundle = sGlobals.mService.getCurrentBitmapCrops(which, mContext.getUserId()); + SparseArray result = new SparseArray<>(); + if (bundle == null) return result; + for (String key : bundle.keySet()) { + int intKey = Integer.parseInt(key); + Rect rect = bundle.getParcelable(key, Rect.class); + result.put(intKey, rect); + } + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * For preview purposes. * Return how a bitmap of a given size would be cropped for a given list of display sizes, if @@ -1664,7 +1722,8 @@ public class WallpaperManager { * @hide */ @FlaggedApi(FLAG_MULTI_CROP) - @Nullable + @TestApi + @NonNull public List getBitmapCrops(@NonNull Point bitmapSize, @NonNull List displaySizes, @Nullable Map cropHints) { try { @@ -1890,9 +1949,14 @@ public class WallpaperManager { * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. * @param getCropped If true the cropped file will be retrieved, if false the original will * be retrieved. - * + * @return A ParcelFileDescriptor for the wallpaper bitmap of the given destination, if it's an + * ImageWallpaper wallpaper. Return {@code null} if the wallpaper is not an + * ImageWallpaper. Also return {@code null} when called with + * which={@link #FLAG_LOCK} if there is a shared home + lock wallpaper. * @hide */ + @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) + @SystemApi @Nullable public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) { return getWallpaperFile(which, mContext.getUserId(), getCropped); @@ -2371,7 +2435,6 @@ public class WallpaperManager { /** * Version of setBitmap that defines how the wallpaper will be positioned for different * display sizes. - * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}. * @param cropHints map from screen dimensions to a sub-region of the image to display for those * dimensions. The {@code Rect} sub-region may have a larger width/height ratio * than the screen dimensions to apply a horizontal parallax effect. If the @@ -2380,6 +2443,7 @@ public class WallpaperManager { * @hide */ @FlaggedApi(FLAG_MULTI_CROP) + @TestApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map cropHints, boolean allowBackup, @SetWallpaperFlags int which) throws IOException { @@ -2562,7 +2626,6 @@ public class WallpaperManager { /** * Version of setStream that defines how the wallpaper will be positioned for different * display sizes. - * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}. * @param cropHints map from screen dimensions to a sub-region of the image to display for those * dimensions. The {@code Rect} sub-region may have a larger width/height ratio * than the screen dimensions to apply a horizontal parallax effect. If the @@ -2571,9 +2634,11 @@ public class WallpaperManager { * @hide */ @FlaggedApi(FLAG_MULTI_CROP) + @TestApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) - public int setStreamWithCrops(InputStream bitmapData, @NonNull Map cropHints, - boolean allowBackup, @SetWallpaperFlags int which) throws IOException { + public int setStreamWithCrops(@NonNull InputStream bitmapData, + @NonNull Map cropHints, boolean allowBackup, @SetWallpaperFlags int which) + throws IOException { SparseArray crops = new SparseArray<>(); cropHints.forEach((k, v) -> crops.put(getOrientation(k), v)); return setStreamWithCrops(bitmapData, crops, allowBackup, which); @@ -2583,15 +2648,21 @@ public class WallpaperManager { * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since * WallpaperBackupAgent stores orientations rather than the exact display size. - * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}. + * @param bitmapData A stream containing the raw data to install as a wallpaper. This + * data can be in any format handled by {@link BitmapRegionDecoder}. * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display * for that screen orientation. + * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper + * image for restore to a future device; {@code false} otherwise. + * @param which Flags indicating which wallpaper(s) to configure with the new imagery. * @hide */ @FlaggedApi(FLAG_MULTI_CROP) + @SystemApi @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) - public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray cropHints, - boolean allowBackup, @SetWallpaperFlags int which) throws IOException { + public int setStreamWithCrops(@NonNull InputStream bitmapData, + @NonNull SparseArray cropHints, boolean allowBackup, @SetWallpaperFlags int which) + throws IOException { if (sGlobals.mService == null) { Log.w(TAG, "WallpaperService not running"); throw new RuntimeException(new DeadSystemException()); diff --git a/core/java/android/app/ZenBypassingApp.java b/core/java/android/app/ZenBypassingApp.java new file mode 100644 index 0000000000000000000000000000000000000000..89bcfa2d2e7dd52abbc5b88177cd35877e6b99e7 --- /dev/null +++ b/core/java/android/app/ZenBypassingApp.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * @hide + */ +public final class ZenBypassingApp implements Parcelable { + + @NonNull private String mPkg; + private boolean mAllChannelsBypass; + + + public ZenBypassingApp(@NonNull String pkg, boolean allChannelsBypass) { + mPkg = pkg; + mAllChannelsBypass = allChannelsBypass; + } + + public ZenBypassingApp(Parcel source) { + mPkg = source.readString(); + mAllChannelsBypass = source.readBoolean(); + } + + @NonNull + public String getPkg() { + return mPkg; + } + + public boolean doAllChannelsBypass() { + return mAllChannelsBypass; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPkg); + dest.writeBoolean(mAllChannelsBypass); + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public ZenBypassingApp createFromParcel(Parcel source) { + return new ZenBypassingApp(source); + } + @Override + public ZenBypassingApp[] newArray(int size) { + return new ZenBypassingApp[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ZenBypassingApp)) return false; + ZenBypassingApp that = (ZenBypassingApp) o; + return mAllChannelsBypass == that.mAllChannelsBypass && Objects.equals(mPkg, + that.mPkg); + } + + @Override + public int hashCode() { + return Objects.hash(mPkg, mAllChannelsBypass); + } + + @Override + public String toString() { + return "ZenBypassingApp{" + + "mPkg='" + mPkg + '\'' + + ", mAllChannelsBypass=" + mAllChannelsBypass + + '}'; + } +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 102540c010aeec23a21c2234d7bd68bb90fcb72f..e766ae2fce0db1fa66b963d23b7e45bcb761bbbe 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,7 @@ package android.app.admin; +import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.LOCK_DEVICE; @@ -55,8 +56,10 @@ import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; +import static android.app.admin.flags.Flags.FLAG_REMOVE_MANAGED_PROFILE_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; +import static android.app.admin.flags.Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -8918,12 +8921,9 @@ public class DevicePolicyManager { /** * Called by a device owner, a profile owner for the primary user or a profile * owner of an organization-owned managed profile to turn auto time on and off. - * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} - * to prevent the user from changing this setting. *

- * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used, - * no user will be able set the date and time. Instead, the network date - * and time will be used. + * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the + * user from changing this setting, that way no user will be able set the date and time zone. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. @@ -8936,7 +8936,13 @@ public class DevicePolicyManager { throwIfParentInstance("setAutoTimeEnabled"); if (mService != null) { try { - mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled); + if (Flags.setAutoTimeEnabledCoexistence()) { + mService.setAutoTimePolicy(mContext.getPackageName(), + enabled ? DevicePolicyManager.AUTO_TIME_ENABLED + : DevicePolicyManager.AUTO_TIME_DISABLED); + } else { + mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -8965,15 +8971,103 @@ public class DevicePolicyManager { return false; } + /** + * Specifies that the auto time state is not controlled by device policy. + * + * @see #setAutoTimePolicy(ComponentName, int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; + + /** + * Specifies the "disabled" auto time state. + * + * @see #setAutoTimePolicy(ComponentName, int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_DISABLED = 1; + + /** + * Specifies the "enabled" auto time state. + * + * @see #setAutoTimePolicy(ComponentName, int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_ENABLED = 2; + + /** + * Flags supplied to {@link #setAutoTimePolicy}(ComponentName, int)}. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "AUTO_TIME_" }, value = { + AUTO_TIME_NOT_CONTROLLED_BY_POLICY, + AUTO_TIME_DISABLED, + AUTO_TIME_ENABLED + }) + public @interface AutoTimePolicy {} + + /** + * Called by a device owner, a profile owner for the primary user or a profile owner of an + * organization-owned managed profile to turn auto time on and off i.e. Whether time should be + * obtained automatically from the network or not. + *

+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the + * user from changing this setting, that way no user will be able set the date and time zone. + * + * @param policy The desired state among {@link #AUTO_TIME_ENABLED} to enable, + * {@link #AUTO_TIME_DISABLED} to disable and + * {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} to unset the policy. + * @throws SecurityException if caller is not a device owner, a profile owner for the + * primary user, or a profile owner of an organization-owned managed profile, or if the caller + * does not hold the required permission. + */ + @SupportsCoexistence + @RequiresPermission(value = SET_TIME, conditional = true) + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE) + public void setAutoTimePolicy(@AutoTimePolicy int policy) { + throwIfParentInstance("setAutoTimePolicy"); + if (mService != null) { + try { + mService.setAutoTimePolicy(mContext.getPackageName(), policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns current auto time policy's state. + * + * @return One of {@link #AUTO_TIME_ENABLED} if enabled, {@link #AUTO_TIME_DISABLED} if disabled + * and {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} if it's not controlled by + * policy. + * @throws SecurityException if caller is not a device owner, a profile owner for the + * primary user, or a profile owner of an organization-owned managed profile, or if the caller + * does not hold the required permission. + */ + @SupportsCoexistence + @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true) + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE) + public @AutoTimePolicy int getAutoTimePolicy() { + throwIfParentInstance("getAutoTimePolicy"); + if (mService != null) { + try { + return mService.getAutoTimePolicy(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY; + } + /** * Called by a device owner, a profile owner for the primary user or a profile * owner of an organization-owned managed profile to turn auto time zone on and off. - * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} - * to prevent the user from changing this setting. *

- * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used, - * no user will be able set the date and time zone. Instead, the network date - * and time zone will be used. + * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the + * user from changing this setting, that way no user will be able set the date and time zone. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with or Null if the * caller is not a device admin. @@ -8981,13 +9075,17 @@ public class DevicePolicyManager { * @throws SecurityException if caller is not a device owner, a profile owner for the * primary user, or a profile owner of an organization-owned managed profile. */ - @SupportsCoexistence @RequiresPermission(value = SET_TIME_ZONE, conditional = true) public void setAutoTimeZoneEnabled(@Nullable ComponentName admin, boolean enabled) { throwIfParentInstance("setAutoTimeZone"); if (mService != null) { try { - mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled); + if (Flags.setAutoTimeZoneEnabledCoexistence()) { + mService.setAutoTimeZonePolicy(mContext.getPackageName(), + enabled ? AUTO_TIME_ZONE_ENABLED : AUTO_TIME_ZONE_DISABLED ); + } else { + mService.setAutoTimeZoneEnabled(admin, mContext.getPackageName(), enabled); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9016,6 +9114,96 @@ public class DevicePolicyManager { return false; } + /** + * Specifies that the auto time zone state is not controlled by device policy. + * + * @see #setAutoTimeZonePolicy(int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0; + + /** + * Specifies the "disabled" auto time zone state. + * + * @see #setAutoTimeZonePolicy(int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_ZONE_DISABLED = 1; + + /** + * Specifies the "enabled" auto time zone state. + * + * @see #setAutoTimeZonePolicy(int) + */ + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE) + public static final int AUTO_TIME_ZONE_ENABLED = 2; + + /** + * Flags supplied to {@link #setAutoTimeZonePolicy}(int)}. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "AUTO_TIME_ZONE_" }, value = { + AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY, + AUTO_TIME_ZONE_DISABLED, + AUTO_TIME_ZONE_ENABLED + }) + public @interface AutoTimeZonePolicy {} + + /** + * Called by a device owner, a profile owner for the primary user or a profile owner of an + * organization-owned managed profile to turn auto time zone on and off. + *

+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the + * user from changing this setting, that way no user will be able set the date and time zone. + * + * @param policy The desired state among {@link #AUTO_TIME_ZONE_ENABLED} to enable it, + * {@link #AUTO_TIME_ZONE_DISABLED} to disable it or + * {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} to unset the policy. + * @throws SecurityException if caller is not a device owner, a profile owner for the primary + * user, or a profile owner of an organization-owned managed profile, or if the caller does not + * hold the required permission. + */ + @SupportsCoexistence + @RequiresPermission(value = SET_TIME_ZONE, conditional = true) + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE) + public void setAutoTimeZonePolicy(@AutoTimeZonePolicy int policy) { + throwIfParentInstance("setAutoTimeZonePolicy"); + if (mService != null) { + try { + mService.setAutoTimeZonePolicy(mContext.getPackageName(), policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns auto time zone policy's current state. + * + * @return One of {@link #AUTO_TIME_ZONE_ENABLED} if enabled, {@link #AUTO_TIME_ZONE_DISABLED} + * if disabled and {@link #AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY} if the state is not + * controlled by policy. + * @throws SecurityException if caller is not a device owner, a profile owner for the + * primary user, or a profile owner of an organization-owned managed profile, or if the caller + * does not hold the required permission. + */ + @SupportsCoexistence + @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true) + @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ZONE_ENABLED_COEXISTENCE) + public @AutoTimeZonePolicy int getAutoTimeZonePolicy() { + throwIfParentInstance("getAutoTimeZonePolicy"); + if (mService != null) { + try { + return mService.getAutoTimeZonePolicy(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return DevicePolicyManager.AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY; + } + /** * TODO (b/137101239): remove this method in follow-up CL * since it's only used for split system user. @@ -12008,6 +12196,33 @@ public class DevicePolicyManager { } } + /** + * Adds a user restriction globally, specified by the {@code key}. + * + *

Called by a system service only, meaning that the caller's UID must be equal to + * {@link Process#SYSTEM_UID}. + * + * @param systemEntity The service entity that adds the restriction. A user restriction set by + * a service entity can only be cleared by the same entity. This can be + * just the calling package name, or any string of the caller's choice + * can be used. + * @param key The key of the restriction. + * @throws SecurityException if the caller is not a system service. + * + * @hide + */ + public void addUserRestrictionGlobally(@NonNull String systemEntity, + @NonNull @UserManager.UserRestrictionKey String key) { + if (mService != null) { + try { + mService.setUserRestrictionGloballyFromSystem(systemEntity, key, + /* enable= */ true); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + /** * Called by a profile owner, device owner or a holder of any permission that is associated with * a user restriction to clear a user restriction specified by the key. @@ -12093,6 +12308,33 @@ public class DevicePolicyManager { } } + /** + * Clears a user restriction globally, specified by the {@code key}. + * + *

Called by a system service only, meaning that the caller's UID must be equal to + * {@link Process#SYSTEM_UID}. + * + * @param systemEntity The system entity that clears the restriction. A user restriction + * set by a system entity can only be cleared by the same entity. This + * can be just the calling package name, or any string of the caller's + * choice can be used. + * @param key The key of the restriction. + * @throws SecurityException if the caller is not a system service. + * + * @hide + */ + public void clearUserRestrictionGlobally(@NonNull String systemEntity, + @NonNull @UserManager.UserRestrictionKey String key) { + if (mService != null) { + try { + mService.setUserRestrictionGloballyFromSystem(systemEntity, key, + /* enable= */ false); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + /** * Called by an admin to get user restrictions set by themselves with * {@link #addUserRestriction(ComponentName, String)}. @@ -12550,28 +12792,43 @@ public class DevicePolicyManager { * @param enabled Whether or not the lockscreen needs to be shown. * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #isSecondaryLockscreenEnabled + * @deprecated Use {@link #setSecondaryLockscreenEnabled(boolean,PersistableBundle)} instead. * @hide - **/ + */ + @Deprecated @SystemApi + @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED) public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) { - setSecondaryLockscreenEnabled(admin, enabled, null); + throwIfParentInstance("setSecondaryLockscreenEnabled"); + if (mService != null) { + try { + mService.setSecondaryLockscreenEnabled(admin, enabled, null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** * Called by the system supervision app to set whether a secondary lockscreen needs to be shown. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the - * caller is not a device admin. + *

The secondary lockscreen will by displayed after the primary keyguard security screen + * requirements are met. + * + *

This API, and associated APIs, can only be called by the default supervision app. + * * @param enabled Whether or not the lockscreen needs to be shown. * @param options A {@link PersistableBundle} to supply options to the lock screen. * @hide */ - public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled, + @SystemApi + @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED) + public void setSecondaryLockscreenEnabled(boolean enabled, @Nullable PersistableBundle options) { throwIfParentInstance("setSecondaryLockscreenEnabled"); if (mService != null) { try { - mService.setSecondaryLockscreenEnabled(admin, enabled, options); + mService.setSecondaryLockscreenEnabled(null, enabled, options); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -16940,11 +17197,14 @@ public class DevicePolicyManager { * @throws SecurityException if the caller does not hold * {@link android.Manifest.permission#MANAGE_PROFILE_AND_DEVICE_OWNERS}. * @throws ProvisioningException if an error occurred during provisioning. + * @deprecated Use {@link #createManagedProfile} and {@link #finalizeCreateManagedProfile} * @hide */ @Nullable @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) public UserHandle createAndProvisionManagedProfile( @NonNull ManagedProfileProvisioningParams provisioningParams) throws ProvisioningException { @@ -16961,6 +17221,93 @@ public class DevicePolicyManager { } } + /** + * Creates a managed profile and sets the + * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile + * owner. The method {@link #finalizeCreateManagedProfile} must be called after to finalize the + * creation of the managed profile. + * + *

The method {@link #checkProvisioningPrecondition} must return {@link #STATUS_OK} + * before calling this method. If it doesn't, a ProvisioningException will be thrown. + * + * @param provisioningParams Params required to provision a managed profile, + * see {@link ManagedProfileProvisioningParams}. + * @return The {@link UserHandle} of the created profile or {@code null} if the service is + * not available. + * @throws ProvisioningException if an error occurred during provisioning. + * @hide + */ + @Nullable + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) + public UserHandle createManagedProfile( + @NonNull ManagedProfileProvisioningParams provisioningParams) + throws ProvisioningException { + if (mService == null) { + return null; + } + try { + return mService.createManagedProfile(provisioningParams, mContext.getPackageName()); + } catch (ServiceSpecificException e) { + throw new ProvisioningException(e, e.errorCode, getErrorMessage(e)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Finalizes the creation of a managed profile by informing the necessary components that + * the managed profile is ready. + * + * @param provisioningParams Params required to provision a managed profile, + * see {@link ManagedProfileProvisioningParams}. + * @param managedProfileUser The recently created managed profile. + * @throws ProvisioningException if an error occurred during provisioning. + * @hide + */ + @SuppressLint("UserHandle") + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED) + public void finalizeCreateManagedProfile( + @NonNull ManagedProfileProvisioningParams provisioningParams, + @NonNull UserHandle managedProfileUser) + throws ProvisioningException { + if (mService == null) { + return; + } + try { + mService.finalizeCreateManagedProfile(provisioningParams, managedProfileUser); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a manged profile from the device only when called from a managed profile's context + * + * @param user UserHandle of the profile to be removed + * @return {@code true} when removal of managed profile was successful, {@code false} when + * removal was unsuccessful or throws IllegalArgumentException when provided user was not a + * managed profile + * @hide + */ + @SystemApi + @UserHandleAware + @FlaggedApi(FLAG_REMOVE_MANAGED_PROFILE_ENABLED) + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean removeManagedProfile() { + if (mService == null) { + throw new IllegalStateException("Could not find DevicePolicyManagerService"); + } + try { + return mService.removeManagedProfile(myUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Called when a managed profile has been provisioned. * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a4e2b8f62a23e54bcdb8833ce46a56259942e79d..d048b5371fc4c0c5ec84416131d9f84524230b9a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -257,6 +257,7 @@ interface IDevicePolicyManager { void setUserRestriction(in ComponentName who, in String callerPackage, in String key, boolean enable, boolean parent); void setUserRestrictionForUser(in String systemEntity, in String key, boolean enable, int targetUser); void setUserRestrictionGlobally(in String callerPackage, in String key); + void setUserRestrictionGloballyFromSystem(in String systemEntity, in String key, boolean enable); Bundle getUserRestrictions(in ComponentName who, in String callerPackage, boolean parent); Bundle getUserRestrictionsGlobally(in String callerPackage); @@ -375,9 +376,15 @@ interface IDevicePolicyManager { void setAutoTimeEnabled(in ComponentName who, String callerPackageName, boolean enabled); boolean getAutoTimeEnabled(in ComponentName who, String callerPackageName); + void setAutoTimePolicy(String callerPackageName, int policy); + int getAutoTimePolicy(String callerPackageName); + void setAutoTimeZoneEnabled(in ComponentName who, String callerPackageName, boolean enabled); boolean getAutoTimeZoneEnabled(in ComponentName who, String callerPackageName); + void setAutoTimeZonePolicy(String callerPackageName, int policy); + int getAutoTimeZonePolicy(String callerPackageName); + void setForceEphemeralUsers(in ComponentName who, boolean forceEpehemeralUsers); boolean getForceEphemeralUsers(in ComponentName who); @@ -563,10 +570,14 @@ interface IDevicePolicyManager { void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId); UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage); + UserHandle createManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage); + void finalizeCreateManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in UserHandle managedProfileUser); void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage); void finalizeWorkProfileProvisioning(in UserHandle managedProfileUser, in Account migratedAccount); + boolean removeManagedProfile(int userId); + void setDeviceOwnerType(in ComponentName admin, in int deviceOwnerType); int getDeviceOwnerType(in ComponentName admin); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index be24bfa41e109b187bd9dae946dd00e52d39f4d8..581efa5d2efab6fbd225a0fc568a0f20aeb36aad 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -343,13 +343,30 @@ flag { } flag { - name: "user_provisioning_same_state" - namespace: "enterprise" - description: "Handle exceptions while setting same provisioning state." - bug: "326441417" - metadata { - purpose: PURPOSE_BUGFIX - } + name: "active_admin_cleanup" + namespace: "enterprise" + description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups" + bug: "335663055" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "user_provisioning_same_state" + namespace: "enterprise" + description: "Handle exceptions while setting same provisioning state." + bug: "326441417" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "remove_managed_profile_enabled" + namespace: "enterprise" + description: "API that removes a given managed profile." + bug: "372652841" } flag { @@ -365,7 +382,7 @@ flag { is_exported: true namespace: "enterprise" description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder." - bug: "376213673" + bug: "358134581" } flag { @@ -374,3 +391,11 @@ flag { description: "Split up existing create and provision managed profile API." bug: "375382324" } + +flag { + name: "secondary_lockscreen_api_enabled" + is_exported: true + namespace: "enterprise" + description: "Add new API for secondary lockscreen" + bug: "336297680" +} diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index dcac59c19df48fbcd6c812717344612dc35b0c74..5004c02194eaf0b94c2741b2f5c2a2fe26acc2d3 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -16,6 +16,8 @@ package android.app.backup; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Intent; @@ -27,6 +29,7 @@ import android.os.RemoteException; import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.ITransportStatusCallback; import com.android.internal.infra.AndroidFuture; +import com.android.server.backup.Flags; import java.util.Arrays; import java.util.List; @@ -670,6 +673,22 @@ public class BackupTransport { return null; } + /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. + * + * @param operationType 0 for backup, 1 for restore. + * @return a subset of the {@code packageNames} passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + */ + @NonNull + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public List getPackagesThatShouldNotUseRestrictedMode( + @NonNull List packageNames, + @BackupAnnotations.OperationType int operationType) { + return List.of(); + } + /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can @@ -977,5 +996,19 @@ public class BackupTransport { resultFuture.cancel(/* mayInterruptIfRunning */ true); } } + + @Override + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void getPackagesThatShouldNotUseRestrictedMode(List packageNames, + int operationType, AndroidFuture> resultFuture) { + try { + List result = + BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames, + operationType); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } + } } } diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java new file mode 100644 index 0000000000000000000000000000000000000000..eea1d2ba5b9ea7f9b4eaebcb67ad6eae315dec97 --- /dev/null +++ b/core/java/android/app/jank/AppJankStats.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This class stores detailed jank statistics for an individual UI widget. These statistics + * provide performance insights for specific UI widget states by correlating the number of + * "Janky frames" with the total frames rendered while the widget is in that state. This class + * can be used by library widgets to provide the system with more detailed information about + * where jank is happening for diagnostic purposes. + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public final class AppJankStats { + // UID of the app + private int mUid; + + // The id that has been set for the widget. + private String mWidgetId; + + // A general category that the widget applies to. + private String mWidgetCategory; + + // The states that the UI elements can report + private String mWidgetState; + + // The number of frames reported during this state. + private long mTotalFrames; + + // Total number of frames determined to be janky during the reported state. + private long mJankyFrames; + + // Histogram of frame duration overruns encoded in predetermined buckets. + private FrameOverrunHistogram mFrameOverrunHistogram; + + + /** Used to indicate no widget category has been set. */ + public static final String WIDGET_CATEGORY_UNSPECIFIED = + "widget_category_unspecified"; + + /** UI elements that facilitate scrolling. */ + public static final String SCROLL = "scroll"; + + /** UI elements that facilitate playing animations. */ + public static final String ANIMATION = "animation"; + + /** UI elements that facilitate media playback. */ + public static final String MEDIA = "media"; + + /** UI elements that facilitate in-app navigation. */ + public static final String NAVIGATION = "navigation"; + + /** UI elements that facilitate displaying, hiding or interacting with keyboard. */ + public static final String KEYBOARD = "keyboard"; + + /** UI elements that facilitate predictive back gesture navigation. */ + public static final String PREDICTIVE_BACK = "predictive_back"; + + /** UI elements that don't fall in one or any of the other categories. */ + public static final String OTHER = "other"; + + /** Used to indicate no widget state has been set. */ + public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified"; + + /** Used to indicate the UI element currently has no state and is idle. */ + public static final String NONE = "none"; + + /** Used to indicate the UI element is currently scrolling. */ + public static final String SCROLLING = "scrolling"; + + /** Used to indicate the UI element is currently being flung. */ + public static final String FLINGING = "flinging"; + + /** Used to indicate the UI element is currently being swiped. */ + public static final String SWIPING = "swiping"; + + /** Used to indicate the UI element is currently being dragged. */ + public static final String DRAGGING = "dragging"; + + /** Used to indicate the UI element is currently zooming. */ + public static final String ZOOMING = "zooming"; + + /** Used to indicate the UI element is currently animating. */ + public static final String ANIMATING = "animating"; + + /** Used to indicate the UI element is currently playing media. */ + public static final String PLAYBACK = "playback"; + + /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */ + public static final String TAPPING = "tapping"; + + + /** + * @hide + */ + @StringDef(value = { + WIDGET_CATEGORY_UNSPECIFIED, + SCROLL, + ANIMATION, + MEDIA, + NAVIGATION, + KEYBOARD, + PREDICTIVE_BACK, + OTHER + }) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Retention(RetentionPolicy.SOURCE) + public @interface WidgetCategory { + } + /** + * @hide + */ + @StringDef(value = { + WIDGET_STATE_UNSPECIFIED, + NONE, + SCROLLING, + FLINGING, + SWIPING, + DRAGGING, + ZOOMING, + ANIMATING, + PLAYBACK, + TAPPING, + }) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Retention(RetentionPolicy.SOURCE) + public @interface WidgetState { + } + + + /** + * Creates a new AppJankStats object. + * + * @param appUid the Uid of the App that is collecting jank stats. + * @param widgetId the widget id that frames will be associated to. + * @param widgetCategory a general functionality category that the widget falls into. Must be + * one of the following: SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, + * PREDICTIVE_BACK, OTHER or will be set to WIDGET_CATEGORY_UNSPECIFIED + * if no value is passed. + * @param widgetState the state the widget was in while frames were counted. Must be one of + * the following: NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, + * ANIMATING, PLAYBACK, TAPPING or will be set to WIDGET_STATE_UNSPECIFIED + * if no value is passed. + * @param totalFrames the total number of frames that were counted for this stat. + * @param jankyFrames the total number of janky frames that were counted for this stat. + * @param frameOverrunHistogram the histogram with predefined buckets. See + * {@link #getFrameOverrunHistogram()} for details. + * + */ + public AppJankStats(int appUid, @NonNull String widgetId, + @Nullable @WidgetCategory String widgetCategory, + @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, + @NonNull FrameOverrunHistogram frameOverrunHistogram) { + mUid = appUid; + mWidgetId = widgetId; + mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; + mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; + mTotalFrames = totalFrames; + mJankyFrames = jankyFrames; + mFrameOverrunHistogram = frameOverrunHistogram; + } + + /** + * Returns the app uid. + * + * @return the app uid. + */ + public int getUid() { + return mUid; + } + + /** + * Returns the id of the widget that reported state changes. + * + * @return the id of the widget that reported state changes. This value cannot be null. + */ + public @NonNull String getWidgetId() { + return mWidgetId; + } + + /** + * Returns the category that the widget's functionality generally falls into, or + * widget_category_unspecified {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in. + * + * @return the category that the widget's functionality generally falls into, this value cannot + * be null. + */ + public @NonNull @WidgetCategory String getWidgetCategory() { + return mWidgetCategory; + } + + /** + * Returns the widget's state that was reported for this stat, or widget_state_unspecified + * {@link #WIDGET_STATE_UNSPECIFIED} if no value was passed in. + * + * @return the widget's state that was reported for this stat. This value cannot be null. + */ + public @NonNull @WidgetState String getWidgetState() { + return mWidgetState; + } + + /** + * Returns the number of frames that were determined to be janky for this stat. + * + * @return the number of frames that were determined to be janky for this stat. + */ + public long getJankyFrameCount() { + return mJankyFrames; + } + + /** + * Returns the total number of frames counted for this stat. + * + * @return the total number of frames counted for this stat. + */ + public long getTotalFrameCount() { + return mTotalFrames; + } + + /** + * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets. + * See {@link FrameOverrunHistogram} for more information. + * + * @return Histogram containing frame overrun times in predefined buckets. This value cannot + * be null. + */ + public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() { + return mFrameOverrunHistogram; + } +} diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java new file mode 100644 index 0000000000000000000000000000000000000000..e28ac126a90af45c597c51171e440644c05489b9 --- /dev/null +++ b/core/java/android/app/jank/FrameOverrunHistogram.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.jank; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; + +import java.util.Arrays; + +/** + * This class is intended to be used when reporting {@link AppJankStats} back to the system. It's + * intended to be used by library widgets to help facilitate the reporting of frame overrun times + * by adding those times into predefined buckets. + */ +@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) +public class FrameOverrunHistogram { + private static int[] sBucketEndpoints = new int[]{ + Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18, + -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40, + 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000 + }; + private int[] mBucketCounts; + + /** + * Create a new instance of FrameOverrunHistogram. + */ + public FrameOverrunHistogram() { + mBucketCounts = new int[sBucketEndpoints.length - 1]; + } + + /** + * Increases the count by one for the bucket representing the frame overrun duration. + * + * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference + * between a frames deadline and when it was rendered. + */ + public void addFrameOverrunMillis(int frameOverrunMillis) { + int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis); + mBucketCounts[countsIndex]++; + } + + /** + * Returns the counts for the all the frame overrun buckets. + * + * @return an array of integers representing the counts of frame overrun times. This value + * cannot be null. + */ + public @NonNull int[] getBucketCounters() { + return Arrays.copyOf(mBucketCounts, mBucketCounts.length); + } + + /** + * Returns the predefined endpoints for the histogram. + * + * @return array of integers representing the endpoints for the predefined histogram count + * buckets. This value cannot be null. + */ + public @NonNull int[] getBucketEndpointsMillis() { + return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length); + } + + // This takes the overrun time and returns what bucket it belongs to in the counters array. + private int getIndexForCountsFromOverrunTime(int overrunTime) { + if (overrunTime < 20) { + if (overrunTime >= -20) { + return (overrunTime + 20) / 2 + 12; + } + if (overrunTime >= -30) { + return (overrunTime + 30) / 5 + 10; + } + if (overrunTime >= -100) { + return (overrunTime + 100) / 10 + 3; + } + if (overrunTime >= -200) { + return (overrunTime + 200) / 50 + 1; + } + return 0; + } + if (overrunTime < 30) { + return (overrunTime - 20) / 5 + 32; + } + if (overrunTime < 100) { + return (overrunTime - 30) / 10 + 34; + } + if (overrunTime < 200) { + return (overrunTime - 50) / 100 + 41; + } + if (overrunTime < 1000) { + return (overrunTime - 200) / 100 + 43; + } + return sBucketEndpoints.length - 1; + } +} diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 981a9167c2da07d1156f4a47b21334321d4ef6d5..7525d0402ee49f4b2fe8915394dea74c36771f25 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -70,8 +70,8 @@ public class JankDataProcessor { for (int j = 0; j < mPendingStates.size(); j++) { StateData pendingState = mPendingStates.get(j); // This state was active during the frame - if (frame.frameVsyncId >= pendingState.mVsyncIdStart - && frame.frameVsyncId <= pendingState.mVsyncIdEnd) { + if (frame.getVsyncId() >= pendingState.mVsyncIdStart + && frame.getVsyncId() <= pendingState.mVsyncIdEnd) { recordFrameCount(frame, pendingState, activityName, appUid); pendingState.mProcessed = true; @@ -86,6 +86,14 @@ public class JankDataProcessor { jankDataProcessingComplete(); } + /** + * Merges app jank stats reported by components outside the platform to the current pending + * stats + */ + public void mergeJankStats(AppJankStats jankStats, String activityName) { + // TODO b/377572463 Add Merging Logic + } + /** * Returns the aggregate map of different pending jank stats. */ @@ -123,14 +131,14 @@ public class JankDataProcessor { mPendingJankStats.put(stateData.mStateDataKey, jankStats); } // This state has already been accounted for - if (jankStats.processedVsyncId == frameData.frameVsyncId) return; + if (jankStats.processedVsyncId == frameData.getVsyncId()) return; jankStats.mTotalFrames += 1; - if (frameData.jankType == JankData.JANK_APPLICATION) { + if ((frameData.getJankType() & JankData.JANK_APPLICATION) != 0) { jankStats.mJankyFrames += 1; } - jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs); - jankStats.processedVsyncId = frameData.frameVsyncId; + jankStats.recordFrameOverrun(frameData.getActualAppFrameTimeNanos()); + jankStats.processedVsyncId = frameData.getVsyncId(); } diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java index df422e0069c5a9f5a73ee07a2bd773ffffc26e29..202281f98c972ef4bb20797579c9ba2f9ef4497e 100644 --- a/core/java/android/app/jank/JankTracker.java +++ b/core/java/android/app/jank/JankTracker.java @@ -84,6 +84,14 @@ public class JankTracker { registerWindowListeners(); } + /** + * Merges app jank stats reported by components outside the platform to the current pending + * stats + */ + public void mergeAppJankStats(AppJankStats appJankStats) { + mJankDataProcessor.mergeJankStats(appJankStats, mActivityName); + } + public void setActivityName(@NonNull String activityName) { mActivityName = activityName; } diff --git a/core/java/android/app/keyguard.aconfig b/core/java/android/app/keyguard.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..9cd1c15794160c7dcb6e66de50b22104e86aafc4 --- /dev/null +++ b/core/java/android/app/keyguard.aconfig @@ -0,0 +1,10 @@ +package: "android.app" +container: "system" + +flag { + namespace: "wallet_integration" + name: "device_unlock_listener" + is_exported: true + description: "Enable listener API for device unlock." + bug: "296195355" +} \ No newline at end of file diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index ee93870be0552378f66a9e34c368328a140e6af3..2e3d5e15e03742e263b2bf7b4f7dd053678d2e0e 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -12,6 +12,13 @@ flag { bug: "371174789" } +flag { + name: "notifications_redesign_templates" + namespace: "systemui" + description: "Notifications Redesign: Update notification templates" + bug: "378660052" +} + flag { name: "modes_api" is_exported: true @@ -99,16 +106,6 @@ flag { bug: "330214226" } -flag { - name: "visit_person_uri" - namespace: "systemui" - description: "Guards the security fix that ensures all URIs Person.java are valid" - bug: "281044385" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "notification_expansion_optional" namespace: "systemui" @@ -278,3 +275,10 @@ flag { description: "Adds UI for NAS classification of notifications" bug: "367996732" } + +flag { + name: "no_sbnholder" + namespace: "systemui" + description: "removes sbnholder from NLS" + bug: "362981561" +} diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig index 8b6441ae5a7c44592a5344b180be10677d52894d..74a96c864167cad125d9feaaec0906fd50a20050 100644 --- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig +++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig @@ -8,3 +8,10 @@ flag { description: "Make methods on OnDeviceIntelligenceManager available for local inference." bug: "304755128" } +flag { + name: "enable_on_device_intelligence_module" + is_exported: true + namespace: "ondeviceintelligence" + description: "Enable migration to mainline module and related changes." + bug: "376427781" +} \ No newline at end of file diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig index 61b53f97fea1658aa7b2a789917df41f7403efc9..359c84eeb55923847dfd62bc476d0c5b1b934c0a 100644 --- a/core/java/android/app/performance.aconfig +++ b/core/java/android/app/performance.aconfig @@ -35,3 +35,10 @@ flag { bug: "373752556" } +flag { + namespace: "system_performance" + name: "pic_cache_nulls" + is_fixed_read_only: true + description: "Cache null returns from binder calls" + bug: "372923336" +} diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/core/java/android/app/supervision/SupervisionManagerInternal.java similarity index 70% rename from services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java rename to core/java/android/app/supervision/SupervisionManagerInternal.java index fead05bc7e492c5da60b0e5d885c722363bc42ff..d571e14ff5faacd7ae49b6f819f24072b514eabf 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java +++ b/core/java/android/app/supervision/SupervisionManagerInternal.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.server.supervision; +package android.app.supervision; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.os.Bundle; +import android.os.PersistableBundle; /** * Local system service interface for {@link SupervisionService}. @@ -34,6 +34,19 @@ public abstract class SupervisionManagerInternal { */ public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId); + /** + * Returns whether the supervision lock screen needs to be shown. + */ + public abstract boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId); + + /** + * Set whether supervision is enabled for the specified user. + * + * @param userId The user to set the supervision state for + * @param enabled Whether or not the user should be supervised + */ + public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled); + /** * Sets whether the supervision lock screen should be shown for the specified user * @@ -42,5 +55,5 @@ public abstract class SupervisionManagerInternal { * @param options Optional configuration parameters for the supervision lock screen */ public abstract void setSupervisionLockscreenEnabledForUser( - @UserIdInt int userId, boolean enabled, @Nullable Bundle options); + @UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options); } diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index bcb5b3636c958c0f921886a7cb72c2fe0ed02e40..d5e696d49ff41cb78fbc2ff0febe25e61aa6e54c 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -7,4 +7,12 @@ flag { namespace: "supervision" description: "Flag to enable the SupervisionService" bug: "340351729" -} \ No newline at end of file +} + +flag { + name: "supervision_api_on_wear" + is_exported: true + namespace: "supervision" + description: "Flag to enable the SupervisionService on Wear devices" + bug: "373358935" +} diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 730bb73da3bbd478bbb7f4186c2f11f490b4fe46..ffa54881cb9db5c39ddcf04939fa8564bf662242 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -18,6 +18,7 @@ package android.app.trust; import android.app.trust.ITrustListener; import android.hardware.biometrics.BiometricSourceType; +import com.android.internal.policy.IDeviceLockedStateListener; /** * System private API to comunicate with trust service. @@ -43,4 +44,8 @@ interface ITrustManager { boolean isActiveUnlockRunning(int userId); @EnforcePermission("ACCESS_FINE_LOCATION") boolean isInSignificantPlace(); + @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE") + void registerDeviceLockedStateListener(in IDeviceLockedStateListener listener, int deviceId); + @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE") + void unregisterDeviceLockedStateListener(in IDeviceLockedStateListener listener); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 1ef83cdf3f85e204e5a69c3f2cf8defc1aef58cd..8c8970ea58343dc0a7c8504d5b81fd320d6fce6e 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -31,6 +31,8 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; +import com.android.internal.policy.IDeviceLockedStateListener; + import java.util.ArrayList; import java.util.List; @@ -258,6 +260,35 @@ public class TrustManager { } } + /** + * Registers a listener for device lock state events. + * + * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} + * permission. + */ + public void registerDeviceLockedStateListener(final IDeviceLockedStateListener listener, + int deviceId) { + try { + mService.registerDeviceLockedStateListener(listener, deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a listener for device lock state events. + * + * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE} + * permission. + */ + public void unregisterDeviceLockedStateListener(final IDeviceLockedStateListener listener) { + try { + mService.unregisterDeviceLockedStateListener(listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}. diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig index 4b880d03041348cc8f0798efaa9ec124d43fcd55..f750a844f4ffd295f83cc03d3bb2fe84e9da7050 100644 --- a/core/java/android/app/wallpaper.aconfig +++ b/core/java/android/app/wallpaper.aconfig @@ -22,3 +22,21 @@ flag { bug: "347235611" is_exported: true } + +flag { + name: "customization_packs_apis" + is_exported: true + namespace: "systemui" + description: "Move APIs related to bitmap and crops to @SystemApi." + bug: "372344184" +} + +flag { + name: "accurate_wallpaper_downsampling" + namespace: "systemui" + description: "Accurate downsampling of wallpaper bitmap for high resolution images" + bug: "355665230" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index 8ffda7242b373ddb42366919343c21fb85ca6efb..4a142bb5287afb94d001ed3a32a0d1ea0cbef688 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -113,7 +113,7 @@ public final class WallpaperDescription implements Parcelable { /** @return the description for this wallpaper */ @NonNull public List getDescription() { - return new ArrayList<>(); + return mDescription; } /** @return the {@link Uri} for the action associated with the wallpaper, or {@code null} if not diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index ce515761551c140b1bfd95bcdac78a6c280e939e..fb33348d9c26474434f98d4302a9b948da8fe6e1 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -92,3 +92,13 @@ flag { is_exported: true is_fixed_read_only: true } + +flag { + name: "check_remote_views_uri_permission" + namespace: "app_widgets" + description: "Check that the widget provider has permissions to access any URIs within its RemoteViews" + bug: "369137473" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index c47fe236faf0b36a62392b2fe37f956ba1047451..de01280f293f6d1eec5b3370840a410054ae7d39 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -146,6 +146,14 @@ flag { is_exported: true } +flag { + namespace: "virtual_devices" + name: "notifications_for_device_streaming" + description: "Add notifications permissions to device streaming role" + bug: "375240276" + is_exported: true +} + flag { namespace: "virtual_devices" name: "default_device_camera_access_policy" diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 964a8be0f153f912ed953cfdf0956e4d7b1997f6..a81629445263bb69960fa8f071aadc9e7ef39490 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -356,7 +356,6 @@ public abstract class BroadcastReceiver { } RuntimeException e = new RuntimeException( "BroadcastReceiver trying to return result during a non-ordered broadcast"); - e.fillInStackTrace(); Log.e("BroadcastReceiver", e.getMessage(), e); } } @@ -768,7 +767,6 @@ public abstract class BroadcastReceiver { } RuntimeException e = new RuntimeException( "BroadcastReceiver trying to return result during a non-ordered broadcast"); - e.fillInStackTrace(); Log.e("BroadcastReceiver", e.getMessage(), e); } } diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index ff0bb25bbcccef951c6a88a020da8604b98175e7..cc57dc05d6b10664d6c81ba11a5a8cb2343517ca 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -398,6 +398,7 @@ public class ClipData implements Parcelable { * Retrieve the raw Intent contained in this Item. */ public Intent getIntent() { + Intent.maybeMarkAsMissingCreatorToken(mIntent); return mIntent; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6fa5a9b8285881ed05e764799fc739eb0a650228..88533049f97058ebe4a06f13b47ff22a350fc751 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -108,6 +108,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.TimeZone; +import java.util.function.Consumer; /** * An intent is an abstract description of an operation to be performed. It @@ -892,6 +893,20 @@ public class Intent implements Parcelable, Cloneable { public static void maybeMarkAsMissingCreatorToken(Object object) { if (object instanceof Intent intent) { maybeMarkAsMissingCreatorTokenInternal(intent); + } else if (object instanceof Parcelable[] parcelables) { + for (Parcelable p : parcelables) { + if (p instanceof Intent intent) { + maybeMarkAsMissingCreatorTokenInternal(intent); + } + } + } else if (object instanceof ArrayList parcelables) { + int N = parcelables.size(); + for (int i = 0; i < N; i++) { + Object p = parcelables.get(i); + if (p instanceof Intent intent) { + maybeMarkAsMissingCreatorTokenInternal(intent); + } + } } } @@ -12204,7 +12219,68 @@ public class Intent implements Parcelable, Cloneable { // Stores a creator token for an intent embedded as an extra intent in a top level intent, private IBinder mCreatorToken; // Stores all extra keys whose values are intents for a top level intent. - private ArraySet mExtraIntentKeys; + private ArraySet mNestedIntentKeys; + } + + /** + * @hide + */ + public static class NestedIntentKey { + /** @hide */ + @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = { + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, + NESTED_INTENT_KEY_TYPE_CLIP_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface NestedIntentKeyType { + } + + /** + * This flag indicates the key is for an extra parcel in mExtras. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0; + + /** + * This flag indicates the key is for an extra parcel array in mExtras and the index is the + * index of that array. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1; + + /** + * This flag indicates the key is for an extra parcel list in mExtras and the index is the + * index of that list. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2; + + /** + * This flag indicates the key is for an extra parcel in mClipData.mItems. + */ + private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3; + + private final @NestedIntentKeyType int mType; + private final String mKey; + private final int mIndex; + + private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) { + this.mType = type; + this.mKey = key; + this.mIndex = index; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NestedIntentKey that = (NestedIntentKey) o; + return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mKey, mIndex); + } } private @Nullable CreatorTokenInfo mCreatorTokenInfo; @@ -12227,8 +12303,8 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ - public Set getExtraIntentKeys() { - return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys; + public Set getExtraIntentKeys() { + return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys; } /** @hide */ @@ -12246,45 +12322,178 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public void collectExtraIntentKeys() { - if (!preventIntentRedirect()) return; + if (preventIntentRedirect()) { + collectNestedIntentKeysRecur(new ArraySet<>()); + } + } - if (mExtras != null && !mExtras.isEmpty()) { + private void collectNestedIntentKeysRecur(Set visited) { + if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) { for (String key : mExtras.keySet()) { - if (mExtras.get(key) instanceof Intent) { - if (mCreatorTokenInfo == null) { - mCreatorTokenInfo = new CreatorTokenInfo(); - } - if (mCreatorTokenInfo.mExtraIntentKeys == null) { - mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>(); - } - mCreatorTokenInfo.mExtraIntentKeys.add(key); + Object value = mExtras.get(key); + + if (value instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0)); + } else if (value instanceof Parcelable[] parcelables) { + handleParcelableArray(parcelables, key, visited); + } else if (value instanceof ArrayList parcelables) { + handleParcelableList(parcelables, key, visited); } } } + + if (mClipData != null) { + for (int i = 0; i < mClipData.getItemCount(); i++) { + Intent intent = mClipData.getItemAt(i).mIntent; + if (intent != null && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i)); + } + } + } + } + + private void handleNestedIntent(Intent intent, Set visited, NestedIntentKey key) { + visited.add(intent); + if (mCreatorTokenInfo == null) { + mCreatorTokenInfo = new CreatorTokenInfo(); + } + if (mCreatorTokenInfo.mNestedIntentKeys == null) { + mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(); + } + mCreatorTokenInfo.mNestedIntentKeys.add(key); + intent.collectNestedIntentKeysRecur(visited); + } + + private void handleParcelableArray(Parcelable[] parcelables, String key, Set visited) { + for (int i = 0; i < parcelables.length; i++) { + if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i)); + } + } } + private void handleParcelableList(ArrayList parcelables, String key, Set visited) { + for (int i = 0; i < parcelables.size(); i++) { + if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i)); + } + } + } + + private static final Consumer MARK_TRUSTED_TOKEN_PRESENT_ACTION = intent -> { + intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT; + }; + + private static final Consumer ENABLE_TOKEN_VERIFY_ACTION = intent -> { + if (intent.mExtras != null) { + intent.mExtras.enableTokenVerification(); + } + }; + /** @hide */ public void checkCreatorToken() { - if (mExtras == null) return; - if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) { - for (String key : mCreatorTokenInfo.mExtraIntentKeys) { - try { - Intent extraIntent = mExtras.getParcelable(key, Intent.class); - if (extraIntent == null) { - Log.w(TAG, "The key {" + key - + "} does not correspond to an intent in the bundle."); - continue; + forEachNestedCreatorToken(MARK_TRUSTED_TOKEN_PRESENT_ACTION, ENABLE_TOKEN_VERIFY_ACTION); + if (mExtras != null) { + // mark the bundle as intent extras after calls to getParcelable. + // otherwise, the logic to mark missing token would run before + // mark trusted creator token present. + mExtras.enableTokenVerification(); + } + } + + /** @hide */ + public void forEachNestedCreatorToken(Consumer action) { + forEachNestedCreatorToken(action, null); + } + + private void forEachNestedCreatorToken(Consumer action, + Consumer postAction) { + if (mExtras == null && mClipData == null) return; + + if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) { + int N = mCreatorTokenInfo.mNestedIntentKeys.size(); + for (int i = 0; i < N; i++) { + NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i); + Intent extraIntent = extractIntentFromKey(key); + + if (extraIntent != null) { + action.accept(extraIntent); + extraIntent.forEachNestedCreatorToken(action); + if (postAction != null) { + postAction.accept(extraIntent); } - extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT; - } catch (Exception e) { - Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e); + } else { + Log.w(TAG, getLogMessageForKey(key)); } } } - // mark the bundle as intent extras after calls to getParcelable. - // otherwise, the logic to mark missing token would run before - // mark trusted creator token present. - mExtras.setIsIntentExtra(); + } + + private Intent extractIntentFromKey(NestedIntentKey key) { + switch (key.mType) { + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL: + return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class); + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY: + if (mExtras == null) return null; + Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class); + if (extraIntents != null && key.mIndex < extraIntents.length) { + return extraIntents[key.mIndex]; + } + break; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST: + if (mExtras == null) return null; + ArrayList extraIntentsList = mExtras.getParcelableArrayList(key.mKey, + Intent.class); + if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) { + return extraIntentsList.get(key.mIndex); + } + break; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA: + if (mClipData == null) return null; + if (key.mIndex < mClipData.getItemCount()) { + ClipData.Item item = mClipData.getItemAt(key.mIndex); + if (item != null) { + return item.mIntent; + } + } + break; + } + return null; + } + + private String getLogMessageForKey(NestedIntentKey key) { + switch (key.mType) { + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL: + return "The key {" + key + "} does not correspond to an intent in the bundle."; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY: + if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) { + return "The key {" + key + + "} does not correspond to a Parcelable[] in the bundle."; + } else { + return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent."; + } + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST: + if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) { + return "The key {" + key + + "} does not correspond to an ArrayList in the bundle."; + } else { + return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent."; + } + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA: + if (key.mIndex >= mClipData.getItemCount()) { + return "Index out of range for clipData items. index: " + key.mIndex + + ". item counts: " + mClipData.getItemCount(); + } else { + return "clipData items at index [" + key.mIndex + + "] is null or does not contain an intent."; + } + default: + return "Unknown key type: " + key.mType; + } } /** @@ -12357,7 +12566,19 @@ public class Intent implements Parcelable, Cloneable { } else { out.writeInt(1); out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken); - out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys); + + if (mCreatorTokenInfo.mNestedIntentKeys != null) { + final int N = mCreatorTokenInfo.mNestedIntentKeys.size(); + out.writeInt(N); + for (int i = 0; i < N; i++) { + NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i); + out.writeInt(key.mType); + out.writeString8(key.mKey); + out.writeInt(key.mIndex); + } + } else { + out.writeInt(0); + } } } } @@ -12422,7 +12643,18 @@ public class Intent implements Parcelable, Cloneable { if (in.readInt() != 0) { mCreatorTokenInfo = new CreatorTokenInfo(); mCreatorTokenInfo.mCreatorToken = in.readStrongBinder(); - mCreatorTokenInfo.mExtraIntentKeys = (ArraySet) in.readArraySet(null); + + N = in.readInt(); + if (N > 0) { + mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N); + for (int i = 0; i < N; i++) { + int type = in.readInt(); + String key = in.readString8(); + int index = in.readInt(); + mCreatorTokenInfo.mNestedIntentKeys.append( + new NestedIntentKey(type, key, index)); + } + } } } } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 54c5596623a29e18b034f978ecaba332dd79473f..63279af8480d85c3b25dd155e0c7e0e590ae0af7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2936,6 +2936,8 @@ public class PackageInstaller { public @Nullable String dexoptCompilerFilter = null; /** {@hide} */ public boolean forceVerification; + /** {@hide} */ + public boolean isAutoInstallDependenciesEnabled = true; private final ArrayMap mPermissionStates; @@ -2991,6 +2993,7 @@ public class PackageInstaller { unarchiveId = source.readInt(); dexoptCompilerFilter = source.readString(); forceVerification = source.readBoolean(); + isAutoInstallDependenciesEnabled = source.readBoolean(); } /** {@hide} */ @@ -3028,6 +3031,7 @@ public class PackageInstaller { ret.unarchiveId = unarchiveId; ret.dexoptCompilerFilter = dexoptCompilerFilter; ret.forceVerification = forceVerification; + ret.isAutoInstallDependenciesEnabled = isAutoInstallDependenciesEnabled; return ret; } @@ -3744,6 +3748,23 @@ public class PackageInstaller { this.forceVerification = true; } + /** + * Optionally indicate whether missing SDK or static shared library dependencies should be + * automatically fetched and installed when installing an app that wants to use these + * dependencies. + * + *

This feature is enabled by default. + * + * @param enableAutoInstallDependencies {@code true} to enable auto-installation of missing + * SDK or static shared library dependencies, + * {@code false} to disable and fail immediately if + * dependencies aren't already installed. + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public void setEnableAutoInstallDependencies(boolean enableAutoInstallDependencies) { + isAutoInstallDependenciesEnabled = enableAutoInstallDependencies; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -3780,6 +3801,7 @@ public class PackageInstaller { pw.printPair("unarchiveId", unarchiveId); pw.printPair("dexoptCompilerFilter", dexoptCompilerFilter); pw.printPair("forceVerification", forceVerification); + pw.printPair("isAutoInstallDependenciesEnabled", isAutoInstallDependenciesEnabled); pw.println(); } @@ -3827,6 +3849,7 @@ public class PackageInstaller { dest.writeInt(unarchiveId); dest.writeString(dexoptCompilerFilter); dest.writeBoolean(forceVerification); + dest.writeBoolean(isAutoInstallDependenciesEnabled); } public static final Parcelable.Creator @@ -4004,6 +4027,9 @@ public class PackageInstaller { private int mSessionErrorCode; private String mSessionErrorMessage; + /** {@hide} */ + public boolean isAutoInstallingDependenciesEnabled; + /** {@hide} */ public boolean isCommitted; @@ -4097,6 +4123,7 @@ public class PackageInstaller { packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); pendingUserActionReason = source.readInt(); + isAutoInstallingDependenciesEnabled = source.readBoolean(); } /** @@ -4681,6 +4708,16 @@ public class PackageInstaller { return (installFlags & PackageManager.INSTALL_UNARCHIVE) != 0; } + /** + * Check whether missing SDK or static shared library dependencies should be automatically + * fetched and installed when installing an app that wants to use these dependencies. + * + * @return true if the dependencies will be auto-installed, false otherwise. + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public boolean isAutoInstallDependenciesEnabled() { + return isAutoInstallingDependenciesEnabled; + } @Override public int describeContents() { @@ -4735,6 +4772,7 @@ public class PackageInstaller { dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(pendingUserActionReason); + dest.writeBoolean(isAutoInstallingDependenciesEnabled); } public static final Parcelable.Creator diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3152ff4564fe7ef55473d0edadc5cd0595d0f679..37295ac94823f89f11f44defb7b0dadf00e1c6d1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -192,6 +192,42 @@ public abstract class PackageManager { public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; + /** + * <application> level {@link android.content.pm.PackageManager.Property} tag + * specifying whether the app should be put into the "restricted" backup mode when it's started + * for backup and restore operations. + * + *

See for + * information about restricted mode. + * + *

Starting with Android 16 apps may not be started in restricted mode based on this + * property. + * + *

Syntax: + *

+     * <application>
+     *   <property
+     *     android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"
+     *     android:value="true|false"/>
+     * </application>
+     * 
+ * + *

If this property is set, the operating system will respect it for now (see Note below). + * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps + * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the + * property defaults to {@code true}. For apps targeting SDK level + * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a + * decision dynamically. + * + *

Note: It's not recommended to set this property to {@code true} unless absolutely + * necessary. In a future Android version, this property may be deprecated in favor of removing + * restricted mode completely. + */ + @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = + "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; + /** * Application level property that an app can specify to opt-out from having private data * directories both on the internal and external storages. @@ -292,6 +328,10 @@ public abstract class PackageManager { *

* The value of a property will only have a single type, as defined by * the property itself. + * + *

Note: + * In android version {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and earlier, + * the {@code equals} and {@code hashCode} methods for this class may not function as expected. */ public static final class Property implements Parcelable { private static final int TYPE_BOOLEAN = 1; @@ -523,6 +563,40 @@ public abstract class PackageManager { return new Property[size]; } }; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Property)) { + return false; + } + final Property property = (Property) obj; + return mType == property.mType && + Objects.equals(mName, property.mName) && + Objects.equals(mClassName, property.mClassName) && + Objects.equals(mPackageName, property.mPackageName) && + (mType == TYPE_BOOLEAN ? mBooleanValue == property.mBooleanValue : + mType == TYPE_FLOAT ? Float.compare(mFloatValue, property.mFloatValue) == 0 : + mType == TYPE_INTEGER ? mIntegerValue == property.mIntegerValue : + mType == TYPE_RESOURCE ? mIntegerValue == property.mIntegerValue : + mStringValue.equals(property.mStringValue)); + } + + @Override + public int hashCode() { + int result = Objects.hash(mName, mType, mClassName, mPackageName); + if (mType == TYPE_BOOLEAN) { + result = 31 * result + (mBooleanValue ? 1 : 0); + } else if (mType == TYPE_FLOAT) { + result = 31 * result + Float.floatToIntBits(mFloatValue); + } else if (mType == TYPE_INTEGER) { + result = 31 * result + mIntegerValue; + } else if (mType == TYPE_RESOURCE) { + result = 31 * result + mIntegerValue; + } else if (mType == TYPE_STRING) { + result = 31 * result + mStringValue.hashCode(); + } + return result; + } } /** diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index f7191e605fb84d1fd38d495c7896efa2fcb17f35..5dfec9809f6e81fb891e5d786c3d7d6cccb40d90 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -105,6 +105,8 @@ public final class SharedLibraryInfo implements Parcelable { private final List mOptionalDependentPackages; private List mDependencies; + private final List mCertDigests; + /** * Creates a new instance. * @@ -134,6 +136,7 @@ public final class SharedLibraryInfo implements Parcelable { mDependencies = dependencies; mIsNative = isNative; mOptionalDependentPackages = null; + mCertDigests = null; } /** @@ -165,6 +168,7 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = declaringPackage; mDependencies = dependencies; mIsNative = isNative; + mCertDigests = null; var allDependents = allDependentPackages.first; var usesLibOptional = allDependentPackages.second; @@ -206,6 +210,7 @@ public final class SharedLibraryInfo implements Parcelable { mIsNative = parcel.readBoolean(); mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(), VersionedPackage.class.getClassLoader(), VersionedPackage.class); + mCertDigests = parcel.createStringArrayList(); } /** @@ -214,6 +219,29 @@ public final class SharedLibraryInfo implements Parcelable { * @param versionMajor */ public SharedLibraryInfo(String name, long versionMajor, int type) { + //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal + mPath = null; + mPackageName = null; + mName = name; + mVersion = versionMajor; + mType = type; + mDeclaringPackage = null; + mDependentPackages = null; + mDependencies = null; + mIsNative = false; + mOptionalDependentPackages = null; + mCertDigests = null; + } + + /** + * @hide + * @param name The lib name. + * @param versionMajor The lib major version. + * @param type The type of shared library. + * @param certDigests The list of certificate digests for this shared library. + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public SharedLibraryInfo(String name, long versionMajor, int type, List certDigests) { mPath = null; mPackageName = null; mName = name; @@ -224,6 +252,7 @@ public final class SharedLibraryInfo implements Parcelable { mDependencies = null; mIsNative = false; mOptionalDependentPackages = null; + mCertDigests = certDigests; } /** @@ -433,6 +462,19 @@ public final class SharedLibraryInfo implements Parcelable { return mOptionalDependentPackages; } + /** + * Gets the list of certificate digests for the shared library. + * + * @return The list of certificate digests + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public @NonNull List getCertDigests() { + if (mCertDigests == null) { + return Collections.emptyList(); + } + return mCertDigests; + } + @Override public int describeContents() { return 0; @@ -463,6 +505,7 @@ public final class SharedLibraryInfo implements Parcelable { parcel.writeTypedList(mDependencies); parcel.writeBoolean(mIsNative); parcel.writeParcelableList(mOptionalDependentPackages, flags); + parcel.writeStringList(mCertDigests); } private static String typeToString(int type) { diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..06fcabcf55e922a15d2ea1195097aa9cbb7d1961 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +parcelable DependencyInstallerCallback; \ No newline at end of file diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..ba089f7fd33e16434b80a3492a1bf05e9fa7c590 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.pm.Flags; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * Callbacks for {@link DependencyInstallerService}. The implementation of + * DependencyInstallerService uses this interface to indicate completion of the session creation + * request given by the system server. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) +public final class DependencyInstallerCallback implements Parcelable { + private final IBinder mBinder; + private final IDependencyInstallerCallback mCallback; + + /** @hide */ + public DependencyInstallerCallback(IBinder binder) { + mBinder = binder; + mCallback = IDependencyInstallerCallback.Stub.asInterface(binder); + } + + private DependencyInstallerCallback(Parcel in) { + mBinder = in.readStrongBinder(); + mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder); + } + + /** + * Callback to indicate that all the requested dependencies have been resolved and their + * sessions created. See {@link DependencyInstallerService#onDependenciesRequired}. + * + * @param sessionIds the install session IDs for all requested dependencies + */ + public void onAllDependenciesResolved(@NonNull int[] sessionIds) { + try { + mCallback.onAllDependenciesResolved(sessionIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback to indicate that at least one of the required dependencies could not be resolved + * and any associated sessions have been abandoned. + */ + public void onFailureToResolveAllDependencies() { + try { + mCallback.onFailureToResolveAllDependencies(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeStrongBinder(mBinder); + } + + public static final @NonNull Creator CREATOR = + new Creator<>() { + @Override + public DependencyInstallerCallback createFromParcel(Parcel in) { + return new DependencyInstallerCallback(in); + } + + @Override + public DependencyInstallerCallback[] newArray(int size) { + return new DependencyInstallerCallback[size]; + } + }; +} diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java new file mode 100644 index 0000000000000000000000000000000000000000..11864150e0725ca8019ebc826fc16e6cb78812ae --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.content.pm.Flags; +import android.content.pm.SharedLibraryInfo; +import android.os.IBinder; + +import java.util.List; + +/** + * Service that needs to be implemented by the holder of the DependencyInstaller role. This service + * will be invoked by the system during application installations if it depends on + * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or + * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't + * already installed. + *

+ * Below is an example manifest registration for a {@code DependencyInstallerService}. + *

+ * {@code
+ * 
+ *     ...
+ *     
+ *         
+ *     
+ * 
+ * }
+ * 
+ * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) +public abstract class DependencyInstallerService extends Service { + + private IDependencyInstallerService mBinder; + + @Override + public final @NonNull IBinder onBind(@Nullable Intent intent) { + if (mBinder == null) { + mBinder = new IDependencyInstallerService.Stub() { + @Override + public void onDependenciesRequired(List neededLibraries, + DependencyInstallerCallback callback) { + DependencyInstallerService.this.onDependenciesRequired(neededLibraries, + callback); + } + }; + } + return mBinder.asBinder(); + } + + /** + * Notify the holder of the DependencyInstaller role of the missing dependencies required for + * the completion of an active install session. + * + * @param neededLibraries the list of shared library dependencies needed to be obtained and + * installed. + */ + public abstract void onDependenciesRequired(@NonNull List neededLibraries, + @NonNull DependencyInstallerCallback callback); +} diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..92d1d9e118e632339617712ca640526f53acb887 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import java.util.List; + +/** +* Callbacks for Dependency Installer. The app side invokes on this interface to indicate +* completion of the async dependency install request given by the system server. +* +* {@hide} +*/ +oneway interface IDependencyInstallerCallback { + /** + * Callback to indicate that all the requested dependencies have been resolved and have been + * committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}. + * + * @param sessionIds the install session IDs for all requested dependencies + */ + void onAllDependenciesResolved(in int[] sessionIds); + + /** + * Callback to indicate that at least one of the required dependencies could not be resolved + * and any associated sessions have been abandoned. + */ + void onFailureToResolveAllDependencies(); +} \ No newline at end of file diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..94f5bf45ca9c22cfc9dcac4d55816d029952446e --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.content.pm.dependencyinstaller.DependencyInstallerCallback; +import android.content.pm.SharedLibraryInfo; +import java.util.List; + +/** +* Interface used to communicate with the application code that holds the Dependency Installer role. +* {@hide} +*/ +oneway interface IDependencyInstallerService { + /** + * Notify dependency installer of the required dependencies to complete the current install + * session. + * + * @param neededLibraries the list of shared library dependencies needed to be obtained and + * installed. + */ + void onDependenciesRequired(in List neededLibraries, + in DependencyInstallerCallback callback); + } \ No newline at end of file diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index c424229f479bd3c3ed7265ca60ab1285fdee8596..e181ae8ef3c7d39972a552a436eef467f6127e6b 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -321,6 +321,7 @@ flag { flag { name: "sdk_dependency_installer" + is_exported: true namespace: "package_manager_service" description: "Feature flag to enable installation of missing sdk dependency of app" bug: "370822870" @@ -358,3 +359,20 @@ flag { bug: "377474232" is_fixed_read_only: true } + +flag { + name: "support_minor_versions_in_minsdkversion" + namespace: "package_manager_service" + description: "Block app installations that specify an incompatible minor SDK version" + bug: "377474232" +} + +flag { + name: "app_compat_option_16kb" + is_exported: true + namespace: "devoptions_settings" + description: "Feature flag to enable page size app compat mode from manifest, package manager and settings level." + bug: "371049373" + is_fixed_read_only: true +} + diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 3d89ce12dec48283af807f8daa54ecbf39d2859f..813208d7ff388e52b89d472cca3edaf5f61d8216 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -330,6 +330,17 @@ flag { is_fixed_read_only: true } +flag { + name: "cache_user_info_read_only" + namespace: "multiuser" + description: "Cache UserInfo to avoid unnecessary binder calls" + bug: "161915546" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} + # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile. flag { name: "enable_private_space_features" diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 34c3f5798bc50bb401f349110aa8bf4ac00e93ef..05c8f31df5d62f935ae26df0e92992a5f89122e9 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -38,6 +38,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; +import android.util.EmptyArray; import android.util.Pair; import android.util.Slog; @@ -536,6 +537,9 @@ public class ApkLiteParseUtils { hasBindDeviceAdminPermission); break; case TAG_USES_SDK_LIBRARY: + if (!android.content.pm.Flags.sdkDependencyInstaller()) { + break; + } String usesSdkLibName = parser.getAttributeValue( ANDROID_RES_NAMESPACE, "name"); long usesSdkLibVersionMajor = parser.getAttributeIntValue( @@ -565,10 +569,7 @@ public class ApkLiteParseUtils { usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor, /*allowDuplicates=*/ true); - // We allow ":" delimiters in the SHA declaration as this is the format - // emitted by the certtool making it easy for developers to copy/paste. - // TODO(372862145): Add test for this replacement - usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase(); + usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest); if ("".equals(usesSdkCertDigest)) { // Test-only uses-sdk-library empty certificate digest override. @@ -618,18 +619,23 @@ public class ApkLiteParseUtils { usesStaticLibrariesVersions, usesStaticLibVersion, /*allowDuplicates=*/ true); - // We allow ":" delimiters in the SHA declaration as this is the format - // emitted by the certtool making it easy for developers to copy/paste. - // TODO(372862145): Add test for this replacement - usesStaticLibCertDigest = - usesStaticLibCertDigest.replace(":", "").toLowerCase(); + usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest); + + ParseResult certResult = + parseAdditionalCertificates(input, parser); + if (certResult.isError()) { + return input.error(certResult); + } + String[] additionalCertSha256Digests = certResult.getResult(); + String[] certSha256Digests = + new String[additionalCertSha256Digests.length + 1]; + certSha256Digests[0] = usesStaticLibCertDigest; + System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, + 1, additionalCertSha256Digests.length); - // TODO(372862145): Add support for multiple signer for app targeting - // O-MR1 usesStaticLibrariesCertDigests = ArrayUtils.appendElement( String[].class, usesStaticLibrariesCertDigests, - new String[]{usesStaticLibCertDigest}, - /*allowDuplicates=*/ true); + certSha256Digests, /*allowDuplicates=*/ true); break; case TAG_SDK_LIBRARY: isSdkLibrary = true; @@ -809,6 +815,43 @@ public class ApkLiteParseUtils { declaredLibraries)); } + private static ParseResult parseAdditionalCertificates(ParseInput input, + XmlResourceParser parser) throws XmlPullParserException, IOException { + String[] certSha256Digests = EmptyArray.STRING; + final int depth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > depth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final String nodeName = parser.getName(); + if (nodeName.equals("additional-certificate")) { + String certSha256Digest = parser.getAttributeValue( + ANDROID_RES_NAMESPACE, "certDigest"); + if (TextUtils.isEmpty(certSha256Digest)) { + return input.error("Bad additional-certificate declaration with empty" + + " certDigest:" + certSha256Digest); + } + + certSha256Digest = normalizeCertDigest(certSha256Digest); + certSha256Digests = ArrayUtils.appendElement(String.class, + certSha256Digests, certSha256Digest); + } + } + + return input.success(certSha256Digests); + } + + /** + * We allow ":" delimiters in the SHA declaration as this is the format emitted by the + * certtool making it easy for developers to copy/paste. + */ + private static String normalizeCertDigest(String certDigest) { + return certDigest.replace(":", "").toLowerCase(); + } + private static boolean isDeviceAdminReceiver( XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission) throws XmlPullParserException, IOException { diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 68b5d782bfbf37ba6d23a975c6fb28830f9640c5..908999b649610bcfc6123f19956f92c4d949df68 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -124,11 +124,13 @@ public final class ApkAssets { @Nullable @GuardedBy("this") - private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. + private StringBlock mStringBlock; // null or closed if mNativePtr = 0. @PropertyFlags private final int mFlags; + private final boolean mIsOverlay; + @Nullable private final AssetsProvider mAssets; @@ -302,40 +304,43 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(path, "path"); - mFlags = flags; mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { - mFlags = flags; + this(FORMAT_APK, flags, assets); mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; + } + + private ApkAssets(@FormatType int format, @PropertyFlags int flags, + @Nullable AssetsProvider assets) { + mFlags = flags; mAssets = assets; + mIsOverlay = format == FORMAT_IDMAP; } @UnsupportedAppUsage @@ -425,6 +430,18 @@ public final class ApkAssets { } } + public boolean isSystem() { + return (mFlags & PROPERTY_SYSTEM) != 0; + } + + public boolean isSharedLib() { + return (mFlags & PROPERTY_DYNAMIC) != 0; + } + + public boolean isOverlay() { + return mIsOverlay; + } + @Override public String toString() { return "ApkAssets{path=" + getDebugName() + "}"; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 6fd4d01419774bfcfed30c00d223237a369ebd0b..4551bd52c960827a9e1a6ecc05e42a61b54cde79 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -973,9 +973,9 @@ public final class AssetManager implements AutoCloseable { * Open an asset using ACCESS_STREAMING mode. This provides access to * files that have been bundled with an application as assets -- that is, * files placed in to the "assets" directory. - * + * * @param fileName The name of the asset to open. This name can be hierarchical. - * + * * @see #open(String, int) * @see #list */ @@ -988,10 +988,10 @@ public final class AssetManager implements AutoCloseable { * read its contents. This provides access to files that have been bundled * with an application as assets -- that is, files placed in to the * "assets" directory. - * + * * @param fileName The name of the asset to open. This name can be hierarchical. * @param accessMode Desired access mode for retrieving the data. - * + * * @see #ACCESS_UNKNOWN * @see #ACCESS_STREAMING * @see #ACCESS_RANDOM @@ -1037,14 +1037,14 @@ public final class AssetManager implements AutoCloseable { /** * Return a String array of all the assets at the given path. - * + * * @param path A relative path within the assets, i.e., "docs/home.html". - * + * * @return String[] Array of strings, one for each asset. These file * names are relative to 'path'. You can open the file by * concatenating 'path' and a name in the returned string (via * File) and passing that to open(). - * + * * @see #open */ public @Nullable String[] list(@NonNull String path) throws IOException { @@ -1167,20 +1167,20 @@ public final class AssetManager implements AutoCloseable { return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } } - + /** * Retrieve a parser for a compiled XML file. - * + * * @param fileName The name of the file to retrieve. */ public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName) throws IOException { return openXmlResourceParser(0, fileName); } - + /** * Retrieve a parser for a compiled XML file. - * + * * @param cookie Identifier of the package to be opened. * @param fileName The name of the file to retrieve. */ @@ -1200,7 +1200,7 @@ public final class AssetManager implements AutoCloseable { /** * Retrieve a non-asset as a compiled XML file. Not for use by applications. - * + * * @param fileName The name of the file to retrieve. * @hide */ @@ -1211,7 +1211,7 @@ public final class AssetManager implements AutoCloseable { /** * Retrieve a non-asset as a compiled XML file. Not for use by * applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. * @hide @@ -1675,7 +1675,6 @@ public final class AssetManager implements AutoCloseable { mRefStacks = new HashMap<>(); } RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); mRefStacks.put(id, ex); } mNumRefs++; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index e6b93427f413de415d0b0f78cd65b8cba1716337..bcaceb24d7678f6edb7cae1626dd4cdada7adb6e 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -203,9 +203,25 @@ public class ResourcesImpl { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { - mAssets = assets; - mAppliedSharedLibsHash = - ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); + // Don't reuse assets by default as we have no control over whether they're already + // inside some other ResourcesImpl. + this(assets, metrics, config, displayAdjustments, false); + } + + public ResourcesImpl(@NonNull ResourcesImpl orig) { + // We know for sure that the other assets are in use, so can't reuse the object here. + this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(), + orig.getDisplayAdjustments(), false); + } + + public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, + @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments, + boolean reuseAssets) { + final var assetsAndHash = + ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets, + reuseAssets); + mAssets = assetsAndHash.first; + mAppliedSharedLibsHash = assetsAndHash.second; mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index d2435757756c0e7e539e0ec2681eab5754ccb133..6c35d106bfb7e7e5f4e8a158800c8054e19cb2c9 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -1,6 +1,16 @@ package: "android.credentials.flags" container: "system" +flag { + namespace: "credential_manager" + name: "ttl_fix_enabled" + description: "Enable fix for transaction too large issue" + bug: "371052524" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { namespace: "credential_manager" name: "settings_activity_enabled" diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java index b162ad6e2d156fb3b5d40bee9e8902b60deb7757..6343ba19f56904f50b6b4a8a16ede78b73a763f6 100644 --- a/core/java/android/hardware/DisplayLuts.java +++ b/core/java/android/hardware/DisplayLuts.java @@ -16,116 +16,294 @@ package android.hardware; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.hardware.flags.Flags; import android.util.IntArray; import java.util.ArrayList; -import java.util.List; /** - * @hide + * DisplayLuts provides the developers to apply Lookup Tables (Luts) to a + * {@link android.view.SurfaceControl}. Luts provides ways to control tonemapping + * for specific content. + * + * The general flow is as follows: + *

+ * + *

DisplayLuts flow
+ *

+ * + * @see LutProperties */ +@FlaggedApi(Flags.FLAG_LUTS_API) public final class DisplayLuts { + private ArrayList mEntries; private IntArray mOffsets; private int mTotalLength; - private List mLutBuffers; - private IntArray mLutDimensions; - private IntArray mLutSizes; - private IntArray mLutSamplingKeys; - private static final int LUT_LENGTH_LIMIT = 100000; - + /** + * Create a {@link DisplayLuts} instance. + */ + @FlaggedApi(Flags.FLAG_LUTS_API) public DisplayLuts() { + mEntries = new ArrayList<>(); mOffsets = new IntArray(); mTotalLength = 0; - - mLutBuffers = new ArrayList<>(); - mLutDimensions = new IntArray(); - mLutSizes = new IntArray(); - mLutSamplingKeys = new IntArray(); } - /** - * Add the lut to be applied. - * - * @param buffer - * @param dimension either 1D or 3D - * @param size - * @param samplingKey - */ - public void addLut(@NonNull float[] buffer, @LutProperties.Dimension int dimension, - int size, @LutProperties.SamplingKey int samplingKey) { + @FlaggedApi(Flags.FLAG_LUTS_API) + public static class Entry { + private float[] mBuffer; + private @LutProperties.Dimension int mDimension; + private int mSize; + private @LutProperties.SamplingKey int mSamplingKey; + + private static final int LUT_LENGTH_LIMIT = 100000; + + /** + * Create a Lut entry. + * + *

+ * Noted that 1D Lut(s) are treated as gain curves. + * For 3D Lut(s), 3D Lut(s) are used for direct color manipulations. + * The values of 3D Lut(s) data should be normalized to the range {@code 0.0} + * to {@code 1.0}, inclusive. And 3D Lut(s) data is organized in the order of + * R, G, B channels. + * + * @param buffer The raw lut data + * @param dimension Either 1D or 3D + * @param samplingKey The sampling kay used for the Lut + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public Entry(@NonNull float[] buffer, + @LutProperties.Dimension int dimension, + @LutProperties.SamplingKey int samplingKey) { + if (buffer == null || buffer.length < 1) { + throw new IllegalArgumentException("The buffer cannot be empty!"); + } + + if (buffer.length >= LUT_LENGTH_LIMIT) { + throw new IllegalArgumentException("The lut length is too big to handle!"); + } + + if (dimension != LutProperties.ONE_DIMENSION + && dimension != LutProperties.THREE_DIMENSION) { + throw new IllegalArgumentException("The dimension should be either 1D or 3D!"); + } + + if (dimension == LutProperties.THREE_DIMENSION) { + if (buffer.length <= 3) { + throw new IllegalArgumentException( + "The 3d lut size of each dimension should be over 1!"); + } + int lengthPerChannel = buffer.length; + if (lengthPerChannel % 3 != 0) { + throw new IllegalArgumentException( + "The lut buffer of 3dlut should have 3 channels!"); + } + lengthPerChannel /= 3; - int lutLength = 0; - if (dimension == LutProperties.ONE_DIMENSION) { - lutLength = size; - } else if (dimension == LutProperties.THREE_DIMENSION) { - lutLength = size * size * size; - } else { - clear(); - throw new IllegalArgumentException("The dimension is either 1D or 3D!"); + double size = Math.cbrt(lengthPerChannel); + if (size == (int) size) { + mSize = (int) size; + } else { + throw new IllegalArgumentException( + "Cannot get the cube root of the 3d lut buffer!"); + } + } else { + mSize = buffer.length; + } + + mBuffer = buffer; + mDimension = dimension; + mSamplingKey = samplingKey; } - if (lutLength >= LUT_LENGTH_LIMIT) { - clear(); - throw new IllegalArgumentException("The lut length is too big to handle!"); + /** + * @return the dimension of the lut entry + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public int getDimension() { + return mDimension; } - mOffsets.add(mTotalLength); - mTotalLength += lutLength; + /** + * @return the size of the lut for each dimension + * @hide + */ + public int getSize() { + return mSize; + } + + /** + * @return the lut raw data of the lut + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public @NonNull float[] getBuffer() { + return mBuffer; + } + + /** + * @return the sampling key used by the lut + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public int getSamplingKey() { + return mSamplingKey; + } + + @Override + public String toString() { + return "Entry{" + + "dimension=" + DisplayLuts.Entry.dimensionToString(getDimension()) + + ", size(each dimension)=" + getSize() + + ", samplingKey=" + samplingKeyToString(getSamplingKey()) + "}"; + } + + private static String dimensionToString(int dimension) { + switch(dimension) { + case LutProperties.ONE_DIMENSION: + return "ONE_DIMENSION"; + case LutProperties.THREE_DIMENSION: + return "THREE_DIMENSION"; + default: + return ""; + } + } + + private static String samplingKeyToString(int key) { + switch(key) { + case LutProperties.SAMPLING_KEY_RGB: + return "SAMPLING_KEY_RGB"; + case LutProperties.SAMPLING_KEY_MAX_RGB: + return "SAMPLING_KEY_MAX_RGB"; + default: + return ""; + } + } + } - mLutBuffers.add(buffer); - mLutDimensions.add(dimension); - mLutSizes.add(size); - mLutSamplingKeys.add(samplingKey); + @Override + public String toString() { + StringBuilder sb = new StringBuilder("DisplayLuts{"); + sb.append("\n"); + for (DisplayLuts.Entry entry: mEntries) { + sb.append(entry.toString()); + sb.append("\n"); + } + sb.append("}"); + return sb.toString(); + } + + private void addEntry(Entry entry) { + mEntries.add(entry); + mOffsets.add(mTotalLength); + mTotalLength += entry.getBuffer().length; } private void clear() { - mTotalLength = 0; mOffsets.clear(); - mLutBuffers.clear(); - mLutDimensions.clear(); - mLutSamplingKeys.clear(); + mTotalLength = 0; + mEntries.clear(); + } + + /** + * Set a Lut to be applied. + * + *

Use either this or {@link #set(Entry, Entry)}. The function will + * replace any previously set lut(s).

+ * + * @param entry Either an 1D Lut or a 3D Lut + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public void set(@NonNull Entry entry) { + if (entry == null) { + throw new IllegalArgumentException("The entry is null!"); + } + clear(); + addEntry(entry); + } + + /** + * Set Luts in order to be applied. + * + *

An 1D Lut and 3D Lut will be applied in order. Use either this or + * {@link #set(Entry)}. The function will replace any previously set lut(s)

+ * + * @param first An 1D Lut + * @param second A 3D Lut + */ + @FlaggedApi(Flags.FLAG_LUTS_API) + public void set(@NonNull Entry first, @NonNull Entry second) { + if (first == null || second == null) { + throw new IllegalArgumentException("The entry is null!"); + } + if (first.getDimension() != LutProperties.ONE_DIMENSION + || second.getDimension() != LutProperties.THREE_DIMENSION) { + throw new IllegalArgumentException("The entries should be 1D and 3D in order!"); + } + clear(); + addEntry(first); + addEntry(second); } /** - * @return the array of Lut buffers + * @hide + */ + public boolean valid() { + return mEntries.size() > 0; + } + + /** + * @hide */ public float[] getLutBuffers() { float[] buffer = new float[mTotalLength]; - for (int i = 0; i < mLutBuffers.size(); i++) { - float[] lutBuffer = mLutBuffers.get(i); + for (int i = 0; i < mEntries.size(); i++) { + float[] lutBuffer = mEntries.get(i).getBuffer(); System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length); } return buffer; } /** - * @return the starting point of each lut memory region of the lut buffer + * @hide */ public int[] getOffsets() { return mOffsets.toArray(); } /** - * @return the array of Lut size + * @hide */ public int[] getLutSizes() { - return mLutSizes.toArray(); + int[] sizes = new int[mEntries.size()]; + for (int i = 0; i < mEntries.size(); i++) { + sizes[i] = mEntries.get(i).getSize(); + } + return sizes; } /** - * @return the array of Lut dimension + * @hide */ public int[] getLutDimensions() { - return mLutDimensions.toArray(); + int[] dimensions = new int[mEntries.size()]; + for (int i = 0; i < mEntries.size(); i++) { + dimensions[i] = mEntries.get(i).getDimension(); + } + return dimensions; } /** - * @return the array of sampling key + * @hide */ public int[] getLutSamplingKeys() { - return mLutSamplingKeys.toArray(); + int[] samplingKeys = new int[mEntries.size()]; + for (int i = 0; i < mEntries.size(); i++) { + samplingKeys[i] = mEntries.get(i).getSamplingKey(); + } + return samplingKeys; } } diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java index c9c6d6d08ed2d2dbd158ce3694c3c7dda4326ad9..bf40a415b0f785f5d913af0d6cd452d09aa25fe4 100644 --- a/core/java/android/hardware/LutProperties.java +++ b/core/java/android/hardware/LutProperties.java @@ -16,23 +16,31 @@ package android.hardware; +import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.hardware.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Lut properties class. + * Provides Lut properties of the device. * - * A Lut (Look-Up Table) is a pre-calculated table for color transformation. - * - * @hide + *

+ * A Lut (Look-Up Table) is a pre-calculated table for color correction. + * Applications may be interested in the Lut properties exposed by + * this class to determine if the Lut(s) they select using + * {@link android.view.SurfaceControl.Transaction#setLuts} are by the HWC. + *

*/ +@FlaggedApi(Flags.FLAG_LUTS_API) public final class LutProperties { private final @Dimension int mDimension; private final int mSize; private final @SamplingKey int[] mSamplingKeys; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"SAMPLING_KEY_"}, value = { SAMPLING_KEY_RGB, @@ -42,11 +50,14 @@ public final class LutProperties { } /** use r,g,b channel as the gain value of a Lut */ + @FlaggedApi(Flags.FLAG_LUTS_API) public static final int SAMPLING_KEY_RGB = 0; /** use max of r,g,b channel as the gain value of a Lut */ + @FlaggedApi(Flags.FLAG_LUTS_API) public static final int SAMPLING_KEY_MAX_RGB = 1; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { ONE_DIMENSION, @@ -56,18 +67,22 @@ public final class LutProperties { } /** The Lut is one dimensional */ + @FlaggedApi(Flags.FLAG_LUTS_API) public static final int ONE_DIMENSION = 1; /** The Lut is three dimensional */ + @FlaggedApi(Flags.FLAG_LUTS_API) public static final int THREE_DIMENSION = 3; + @FlaggedApi(Flags.FLAG_LUTS_API) public @Dimension int getDimension() { return mDimension; } /** - * @return the size of the Lut. + * @return the size of the Lut for each dimension */ + @FlaggedApi(Flags.FLAG_LUTS_API) public int getSize() { return mSize; } @@ -75,6 +90,8 @@ public final class LutProperties { /** * @return the list of sampling keys */ + @FlaggedApi(Flags.FLAG_LUTS_API) + @NonNull public @SamplingKey int[] getSamplingKeys() { if (mSamplingKeys.length == 0) { throw new IllegalStateException("no sampling key!"); diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java index 24cfc1b53e00de2bdd861213b564dc8ece23f2ea..d42bfae23d2b0a7f9db5a7336514b20861c84cd8 100644 --- a/core/java/android/hardware/OverlayProperties.java +++ b/core/java/android/hardware/OverlayProperties.java @@ -18,6 +18,7 @@ package android.hardware; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.hardware.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -72,9 +73,11 @@ public final class OverlayProperties implements Parcelable { } /** - * Gets the lut properties of the display. - * @hide + * Returns the lut properties of the device. */ + @FlaggedApi(Flags.FLAG_LUTS_API) + @SuppressLint("ArrayReturn") + @NonNull public LutProperties[] getLutProperties() { if (mNativeObject == 0) { return null; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index a37648f7e45d85bc52589ec175449cf398f86a51..5533a640b9d86aa58412ee4e33a645cf88e00fe8 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -19,9 +19,9 @@ package android.hardware.camera2; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; @@ -560,7 +560,7 @@ public final class CameraCharacteristics extends CameraMetadata * * @return List of CameraCharacteristic keys containing characterisitics specific to a session - * configuration. If {@link #INFO_SESSION_CONFIGURATION_QUERY_VERSION} is + * configuration. If {@link #INFO_SESSION_CONFIGURATION_QUERY_VERSION} is at least * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, then this list will only contain * CONTROL_ZOOM_RATIO_RANGE and SCALER_AVAILABLE_MAX_DIGITAL_ZOOM * @@ -1435,6 +1435,24 @@ public final class CameraCharacteristics extends CameraMetadata> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE = new Key>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference>() {{ }}); + /** + *

List of auto-exposure priority modes for {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} + * that are supported by this camera device.

+ *

This entry lists the valid modes for + * {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for this camera device. + * If no AE priority modes are available for a device, this will only list OFF.

+ *

Range of valid values:
+ * Any value listed in {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode}

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final Key CONTROL_AE_AVAILABLE_PRIORITY_MODES = + new Key("android.control.aeAvailablePriorityModes", int[].class); + /** *

List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera * device.

@@ -5241,6 +5259,32 @@ public final class CameraCharacteristics extends CameraMetadataAll of the above configurations can be set up with a SessionConfiguration. The list of * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.

+ *

When set to BAKLAVA, the additional stream combinations below are verified + * by the compliance tests:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Target 1SizeTarget 2Size
PRIVS1080PPRIVS1080P
PRIVS1080PPRIVS1440P
*

This key is available on all devices.

*/ @PublicKey @@ -6128,6 +6172,66 @@ public final class CameraCharacteristics extends CameraMetadata JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION = new Key("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /** + *

Color space used for shared session configuration for all the output targets + * when camera is opened in shared mode. This should be one of the values specified in + * availableColorSpaceProfilesMap.

+ *

Possible values:

+ *
    + *
  • {@link #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED UNSPECIFIED}
  • + *
  • {@link #SHARED_SESSION_COLOR_SPACE_SRGB SRGB}
  • + *
  • {@link #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 DISPLAY_P3}
  • + *
  • {@link #SHARED_SESSION_COLOR_SPACE_BT2020_HLG BT2020_HLG}
  • + *
+ * + *

Optional - The value for this key may be {@code null} on some devices.

+ * @see #SHARED_SESSION_COLOR_SPACE_UNSPECIFIED + * @see #SHARED_SESSION_COLOR_SPACE_SRGB + * @see #SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 + * @see #SHARED_SESSION_COLOR_SPACE_BT2020_HLG + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final Key SHARED_SESSION_COLOR_SPACE = + new Key("android.sharedSession.colorSpace", int.class); + + /** + *

List of shared output configurations that this camera device supports when + * camera is opened in shared mode. Array contains following entries for each supported + * shared configuration: + * 1) surface type + * 2) width + * 3) height + * 4) format + * 5) mirrorMode + * 6) useReadoutTimestamp + * 7) timestampBase + * 8) dataspace + * 9) usage + * 10) streamUsecase + * 11) physical camera id len + * 12) physical camera id as UTF-8 null terminated string.

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final Key SHARED_SESSION_OUTPUT_CONFIGURATIONS = + new Key("android.sharedSession.outputConfigurations", long[].class); + + /** + *

The available stream configurations that this camera device supports for + * shared capture session when camera is opened in shared mode. Android camera framework + * will generate this tag if the camera device can be opened in shared mode.

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * @hide + */ + @SystemApi + @NonNull + @SyntheticKey + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final Key SHARED_SESSION_CONFIGURATION = + new Key("android.sharedSession.configuration", android.hardware.camera2.params.SharedSessionConfiguration.class); + /** * Mapping from INFO_SESSION_CONFIGURATION_QUERY_VERSION to session characteristics key. diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index fb381d97adc3cc07f1b6451506f5907113d2af19..852f04793f15ef29f2c022b8061f32e0caec9f79 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -446,6 +446,17 @@ public abstract class CameraDevice implements AutoCloseable { public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE; + /** + * Shared camera operation mode. + * + * @see #CameraSharedCaptureSession + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final int SESSION_OPERATION_MODE_SHARED = + 2; // ICameraDeviceUser.SHARED_MODE; + /** * First vendor-specific operating mode * @@ -461,6 +472,7 @@ public abstract class CameraDevice implements AutoCloseable { @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value = {SESSION_OPERATION_MODE_NORMAL, SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED, + SESSION_OPERATION_MODE_SHARED, SESSION_OPERATION_MODE_VENDOR_START}) public @interface SessionOperatingMode {}; @@ -1240,7 +1252,6 @@ public abstract class CameraDevice implements AutoCloseable { * * * - * * @param config A session configuration (see {@link SessionConfiguration}). * * @throws IllegalArgumentException In case the session configuration is invalid; or the output @@ -1558,6 +1569,48 @@ public abstract class CameraDevice implements AutoCloseable { */ public abstract void onOpened(@NonNull CameraDevice camera); // Must implement + /** + * The method called when a camera device has finished opening in shared mode, + * where there can be more than one client accessing the same camera. + * + *

At this point, the camera device is ready to use, and + * {@link CameraDevice#createCaptureSession} can be called to set up the shared capture + * session.

+ * + * @param camera the camera device that has become opened + * @param isPrimaryClient true if the client opening the camera is currently the primary + * client. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void onOpenedInSharedMode(@NonNull CameraDevice camera, boolean isPrimaryClient) { + // Default empty implementation + } + + /** + * The method called when client access priorities have changed for a camera device opened + * in shared mode where there can be more than one client accessing the same camera. + * + * If the client priority changed from secondary to primary, then it can now + * create capture request and change the capture request parameters. If client priority + * changed from primary to secondary, that implies that a higher priority client has also + * opened the camera in shared mode and the new client is now a primary client + * + * @param camera the camera device whose access priorities have changed. + * @param isPrimaryClient true if the client is now the primary client. + * false if another higher priority client also opened the + * camera and is now the new primary client and this client is + * now a secondary client. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void onClientSharedAccessPriorityChanged(@NonNull CameraDevice camera, + boolean isPrimaryClient) { + // Default empty implementation + } + /** * The method called when a camera device has been closed with * {@link CameraDevice#close}. diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b7856303fc053e3deb09d7e679760b46e7cb4459..266efb7b759ceeefda4a4d55979f694c061a2fbd 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -975,6 +975,46 @@ public final class CameraManager { return CameraDeviceSetupImpl.isCameraDeviceSetupSupported(chars); } + /** + * Checks if a camera device can be opened in a shared mode for a given {@code cameraId}. + * If this method returns false for a {@code cameraId}, calling {@link #openSharedCamera} + * for that {@code cameraId} will throw an {@link UnsupportedOperationException}. + * + * @param cameraId The unique identifier of the camera device for which sharing support is + * being queried. This identifier must be present in + * {@link #getCameraIdList()}. + * + * @return {@code true} if camera can be opened in shared mode + * for the provided {@code cameraId}; {@code false} otherwise. + * + * @throws IllegalArgumentException If {@code cameraId} is null, or if {@code cameraId} does not + * match any device in {@link #getCameraIdList()}. + * @throws CameraAccessException if the camera device has been disconnected. + * + * @see #getCameraIdList() + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + @SystemApi + public boolean isCameraDeviceSharingSupported(@NonNull String cameraId) + throws CameraAccessException { + if (cameraId == null) { + throw new IllegalArgumentException("Camera ID was null"); + } + + if (CameraManagerGlobal.sCameraServiceDisabled + || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(), + getDevicePolicyFromContext(mContext))).contains(cameraId)) { + throw new IllegalArgumentException( + "Camera ID '" + cameraId + "' not available on device."); + } + + CameraCharacteristics chars = getCameraCharacteristics(cameraId); + long[] sharedOutputConfiguration = + chars.get(CameraCharacteristics.SHARED_SESSION_OUTPUT_CONFIGURATIONS); + return (sharedOutputConfiguration != null); + } + /** * Retrieves the AttributionSourceState to pass to the CameraService. * @@ -994,7 +1034,7 @@ public final class CameraManager { AttributionSourceState contextAttributionSourceState = contextAttributionSource.asState(); - if (Flags.useContextAttributionSource() && useContextAttributionSource) { + if (Flags.dataDeliveryPermissionChecks() && useContextAttributionSource) { return contextAttributionSourceState; } else { AttributionSourceState clientAttribution = @@ -1036,6 +1076,9 @@ public final class CameraManager { * @param cameraId The unique identifier of the camera device to open * @param callback The callback for the camera. Must not be null. * @param executor The executor to invoke the callback with. Must not be null. + * @param oomScoreOffset The minimum oom score that cameraservice must see for this client. + * @param rotationOverride The type of rotation override. + * @param sharedMode Parameter specifying if the camera should be opened in shared mode. * * @throws CameraAccessException if the camera is disabled by device policy, * too many camera devices are already open, or the cameraId does not match @@ -1051,7 +1094,8 @@ public final class CameraManager { */ private CameraDevice openCameraDeviceUserAsync(String cameraId, CameraDevice.StateCallback callback, Executor executor, - final int oomScoreOffset, int rotationOverride) throws CameraAccessException { + final int oomScoreOffset, int rotationOverride, boolean sharedMode) + throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; synchronized (mLock) { @@ -1070,7 +1114,7 @@ public final class CameraManager { characteristics, this, mContext.getApplicationInfo().targetSdkVersion, - mContext, cameraDeviceSetup); + mContext, cameraDeviceSetup, sharedMode); ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); try { @@ -1091,7 +1135,7 @@ public final class CameraManager { mContext.getApplicationInfo().targetSdkVersion, rotationOverride, clientAttribution, - getDevicePolicyFromContext(mContext)); + getDevicePolicyFromContext(mContext), sharedMode); } catch (ServiceSpecificException e) { if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) { throw new AssertionError("Should've gone down the shim path"); @@ -1218,7 +1262,8 @@ public final class CameraManager { @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler) throws CameraAccessException { - openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler)); + openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), + /*oomScoreOffset*/0, getRotationOverride(mContext), /*sharedMode*/false); } /** @@ -1258,7 +1303,7 @@ public final class CameraManager { /*oomScoreOffset*/0, overrideToPortrait ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT - : ICameraService.ROTATION_OVERRIDE_NONE); + : ICameraService.ROTATION_OVERRIDE_NONE, /*sharedMode*/false); } /** @@ -1303,9 +1348,56 @@ public final class CameraManager { if (executor == null) { throw new IllegalArgumentException("executor was null"); } - openCameraImpl(cameraId, callback, executor); + openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, + getRotationOverride(mContext), /*sharedMode*/false); + } + + /** + * Opens a shared connection to a camera with the given ID. + * + *

The behavior of this method matches that of + * {@link #openCamera(String, Executor, StateCallback)}, except that it opens the camera in + * shared mode where more than one client can access the camera at the same time.

+ * + * @param cameraId The unique identifier of the camera device to open. + * @param executor The executor which will be used when invoking the callback. + * @param callback The callback which is invoked once the camera is opened + * + * @throws CameraAccessException if the camera is disabled by device policy, or is being used + * by a higher-priority client in non-shared mode or the device + * has reached its maximal resource and cannot open this camera + * device. + * + * @throws IllegalArgumentException if cameraId, the callback or the executor was null, + * or the cameraId does not match any currently or previously + * available camera device. + * + * @throws SecurityException if the application does not have permission to + * access the camera + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + * + * @hide + */ + @SystemApi + @RequiresPermission(allOf = { + android.Manifest.permission.SYSTEM_CAMERA, + android.Manifest.permission.CAMERA, + }) + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void openSharedCamera(@NonNull String cameraId, + @NonNull @CallbackExecutor Executor executor, + @NonNull final CameraDevice.StateCallback callback) + throws CameraAccessException { + if (executor == null) { + throw new IllegalArgumentException("executor was null"); + } + openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, + getRotationOverride(mContext), /*sharedMode*/true); } + /** * Open a connection to a camera with the given ID. Also specify what oom score must be offset * by cameraserver for this client. This api can be useful for system @@ -1372,29 +1464,35 @@ public final class CameraManager { "oomScoreOffset < 0, cannot increase priority of camera client"); } openCameraImpl(cameraId, callback, executor, oomScoreOffset, - getRotationOverride(mContext)); + getRotationOverride(mContext), /*sharedMode*/false); } /** * Open a connection to a camera with the given ID, on behalf of another application. - * Also specify the minimum oom score and process state the application - * should have, as seen by the cameraserver. - * - *

The behavior of this method matches that of {@link #openCamera}, except that it allows - * the caller to specify the UID to use for permission/etc verification. This can only be - * done by services trusted by the camera subsystem to act on behalf of applications and - * to forward the real UID.

* + * @param cameraId + * The unique identifier of the camera device to open + * @param callback + * The callback which is invoked once the camera is opened + * @param executor + * The executor which will be used when invoking the callback. * @param oomScoreOffset * The minimum oom score that cameraservice must see for this client. * @param rotationOverride * The type of rotation override (none, override_to_portrait, rotation_only) * that should be followed for this camera id connection + * @param sharedMode + * Parameter specifying if the camera should be opened in shared mode. + * + * @throws CameraAccessException if the camera is disabled by device policy, + * has been disconnected, or is being used by a higher-priority camera API client in + * non shared mode. + * * @hide */ public void openCameraImpl(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, - int oomScoreOffset, int rotationOverride) + int oomScoreOffset, int rotationOverride, boolean sharedMode) throws CameraAccessException { if (cameraId == null) { @@ -1407,24 +1505,7 @@ public final class CameraManager { } openCameraDeviceUserAsync(cameraId, callback, executor, oomScoreOffset, - rotationOverride); - } - - /** - * Open a connection to a camera with the given ID, on behalf of another application. - * - *

The behavior of this method matches that of {@link #openCamera}, except that it allows - * the caller to specify the UID to use for permission/etc verification. This can only be - * done by services trusted by the camera subsystem to act on behalf of applications and - * to forward the real UID.

- * - * @hide - */ - public void openCameraImpl(@NonNull String cameraId, - @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor) - throws CameraAccessException { - openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, - getRotationOverride(mContext)); + rotationOverride, sharedMode); } /** @@ -2541,6 +2622,10 @@ public final class CameraManager { } @Override public void onCameraClosed(String id, int deviceId) { + } + @Override + public void onCameraOpenedInSharedMode(String id, String clientPackageId, + int deviceId, boolean primaryClient) { }}; String[] cameraIds; @@ -3324,6 +3409,11 @@ public final class CameraManager { } } + @Override + public void onCameraOpenedInSharedMode(String cameraId, String clientPackageId, + int deviceId, boolean primaryClient) { + } + @Override public void onCameraOpened(String cameraId, String clientPackageId, int deviceId) { synchronized (mLock) { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 2f6c6a3b02d804790e507ba7a8f17aec55cab84d..d2fcfd62bfca57db050bf6bde72d204ac7c0658b 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -2120,6 +2120,38 @@ public abstract class CameraMetadata { */ public static final int AUTOMOTIVE_LOCATION_EXTRA_RIGHT = 10; + // + // Enumeration values for CameraCharacteristics#SHARED_SESSION_COLOR_SPACE + // + + /** + * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final int SHARED_SESSION_COLOR_SPACE_UNSPECIFIED = -1; + + /** + * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final int SHARED_SESSION_COLOR_SPACE_SRGB = 0; + + /** + * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final int SHARED_SESSION_COLOR_SPACE_DISPLAY_P3 = 7; + + /** + * @see CameraCharacteristics#SHARED_SESSION_COLOR_SPACE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public static final int SHARED_SESSION_COLOR_SPACE_BT2020_HLG = 16; + // // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE // @@ -3396,6 +3428,48 @@ public abstract class CameraMetadata { @FlaggedApi(Flags.FLAG_ZOOM_METHOD) public static final int CONTROL_ZOOM_METHOD_ZOOM_RATIO = 1; + // + // Enumeration values for CaptureRequest#CONTROL_AE_PRIORITY_MODE + // + + /** + *

Disable AE priority mode. This is the default value.

+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE + */ + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final int CONTROL_AE_PRIORITY_MODE_OFF = 0; + + /** + *

The camera device's auto-exposure routine is active and + * prioritizes the application-selected ISO ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}).

+ *

The application has control over {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} while + * the application's values for {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored.

+ * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE + */ + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY = 1; + + /** + *

The camera device's auto-exposure routine is active and + * prioritizes the application-selected exposure time + * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}).

+ *

The application has control over {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} while + * the application's values for {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored.

+ * + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE + */ + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY = 2; + // // Enumeration values for CaptureRequest#EDGE_MODE // diff --git a/core/java/android/hardware/camera2/CameraSharedCaptureSession.java b/core/java/android/hardware/camera2/CameraSharedCaptureSession.java new file mode 100644 index 0000000000000000000000000000000000000000..5426d4d10bec3292483bceb3290eeb9cfc20ec46 --- /dev/null +++ b/core/java/android/hardware/camera2/CameraSharedCaptureSession.java @@ -0,0 +1,180 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.view.Surface; + +import com.android.internal.camera.flags.Flags; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A shared capture session for a {@link CameraDevice}, when a camera device is opened in shared + * mode possibly by multiple clients at the same time. + * + *

An active shared capture session is a special type of capture session used exclusively + * for shared camera access by multiple applications, provided the camera device supports this + * mode. To determine if a camera device supports shared mode, use the + * {@link android.hardware.camera2.CameraManager#isCameraDeviceSharingSupported} API. + * If supported, multiple clients can open the camera by calling + * {@link android.hardware.camera2.CameraManager#openSharedCamera} and create a shared capture + * session by calling {@link CameraDevice#createCaptureSession(SessionConfiguration)} and using + * session type as {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED}

+ * + *

When an application has opened a camera device in shared mode, it can only create a shared + * capture session using session type as + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED}. Any other session + * type value will trigger {@link IllegalArgumentException}. Once the configuration is complete and + * the session is ready to actually capture data, the provided + * {@link CameraCaptureSession.StateCallback}'s + * {@link CameraCaptureSession.StateCallback#onConfigured} callback will be called and will + * receive a CameraCaptureSession (castable to {@link CameraSharedCaptureSession}).

+ * + *

Shared capture sessions uses a predefined configuration detailed in + * {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION}. Using different configuration values + * when creating session will result in an {@link IllegalArgumentException}.

+ * + *

When camera is opened in shared mode, the highest priority client among all the clients will + * be the primary client while the others would be secondary clients. Clients will know if they are + * primary or secondary by the device state callback + * {@link CameraDevice.StateCallback#onOpenedInSharedMode}. Once the camera has been opened in + * shared mode, their access priorities of being a primary or secondary client can change if + * another higher priority client opens the camera later. Once the camera has been opened, + * any change in primary client status will be shared by the device state callback + * {@link CameraDevice.StateCallback#onClientSharedAccessPriorityChanged}.

+ * + *

The priority of client access is determined by considering two factors: its current process + * state and its "out of memory" score. Clients operating in the background are assigned a lower + * priority. In contrast, clients running in the foreground, along with system-level clients, are + * given a higher priority.

+ * + *

Primary clients can create capture requests, modify any capture parameters and send them to + * the capture session for a one-shot capture or as a repeating request using the following apis: + *

+ * + *
    + * + *
  • {@link CameraSharedCaptureSession#capture}
  • + * + *
  • {@link CameraSharedCaptureSession#captureSingleRequest}
  • + * + *
  • {@link CameraSharedCaptureSession#setRepeatingRequest}
  • + * + *
  • {@link CameraSharedCaptureSession#setSingleRepeatingRequest}
  • + * + *
  • {@link CameraSharedCaptureSession#stopRepeating}
  • + * + *
+ * + *

Secondary clients cannot create a capture request and modify any capture parameters. However, + * they can start the camera streaming to desired surface targets using + * {@link CameraSharedCaptureSession#startStreaming}, which will apply default parameters. Once the + * streaming has successfully started, then they can stop the streaming using + * {@link CameraSharedCaptureSession#stopStreaming}.

+ * + *

The following APIs are not supported in shared capture sessions by either the primary or + * secondary client.

+ * + *
    + * + *
  • {@link CameraSharedCaptureSession#captureBurst}
  • + * + *
  • {@link CameraSharedCaptureSession#captureBurstRequests}
  • + * + *
  • {@link CameraSharedCaptureSession#setRepeatingBurst}
  • + * + *
  • {@link CameraSharedCaptureSession#setRepeatingBurstRequests}
  • + * + *
  • {@link CameraSharedCaptureSession#switchToOffline}
  • + * + *
  • {@link CameraSharedCaptureSession#updateOutputConfiguration}
  • + * + *
  • {@link CameraSharedCaptureSession#finalizeOutputConfigurations}
  • + * + *
  • {@link CameraSharedCaptureSession#prepare}
  • + * + *
+ * + * @hide + */ +@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) +@SystemApi +public abstract class CameraSharedCaptureSession extends CameraCaptureSession { + + /** + * Request start of the streaming of camera images by this shared capture session. + * + *

With this method, the camera device will continually capture images + * using the settings provided by primary client if there is ongoing repeating request + * by the primary client or default settings if no ongoing streaming request in progress.

+ * + *

startStreaming has lower priority than the capture requests submitted + * through {@link #capture} by primary client, so if {@link #capture} is called when a + * streaming is active, the capture request will be processed before any further + * streaming requests are processed.

+ * + *

To stop the streaming, call {@link #stopStreaming}

+ * + *

Calling this method will replace any earlier streaming set up by this method.

+ * + * @param surfaces List of target surfaces to use for streaming. + * @param executor The executor which will be used for invoking the listener. + * @param listener The callback object to notify the status and progress of the image capture. + * + * @return int A unique capture sequence ID used by + * {@link CaptureCallback#onCaptureSequenceCompleted}. + * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if this session is no longer active, either because the session + * was explicitly closed, a new session has been created + * or the camera device has been closed. + * @throws IllegalArgumentException If the request references no surfaces or references surfaces + * that are not currently configured as outputs; or + * the executor is null, or the listener is null. + * @see #stopStreaming + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + @SystemApi + public abstract int startStreaming(@NonNull List surfaces, + @NonNull @CallbackExecutor Executor executor, @NonNull CaptureCallback listener) + throws CameraAccessException; + + /** + *

Cancel any ongoing streaming started by {@link #startStreaming}

+ * + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if this session is no longer active, either because the session + * was explicitly closed, a new session has been created + * or the camera device has been closed. + * + * @see #startStreaming + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + @SystemApi + public abstract void stopStreaming() throws CameraAccessException; +} diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 9846cac552120bb414e43b7fe1ee19271a19d500..496d316eb0282c7fbcaba048126e7ab41a514e34 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1406,7 +1406,9 @@ public final class CaptureRequest extends CameraMetadata> * application's selected exposure time, sensor sensitivity, * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and - * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is + * enabled, the relevant priority CaptureRequest settings will not be overridden. + * See {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for more details. If one of the FLASH modes * is selected, the camera device's flash unit controls are * also overridden.

*

The FLASH modes are only available if the camera device @@ -1440,6 +1442,7 @@ public final class CaptureRequest extends CameraMetadata> * * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see CaptureRequest#FLASH_MODE @@ -2705,6 +2708,46 @@ public final class CaptureRequest extends CameraMetadata> public static final Key CONTROL_ZOOM_METHOD = new Key("android.control.zoomMethod", int.class); + /** + *

Turn on AE priority mode.

+ *

This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is + * AUTO and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to one of its + * ON modes, with the exception of ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.

+ *

When a priority mode is enabled, the camera device's + * auto-exposure routine will maintain the application's + * selected parameters relevant to the priority mode while overriding + * the remaining exposure parameters + * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). For example, if + * SENSOR_SENSITIVITY_PRIORITY mode is enabled, the camera device will + * maintain the application-selected {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} + * while adjusting {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} + * and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}. The overridden fields for a + * given capture will be available in its CaptureResult.

+ *

Possible values:

+ *
    + *
  • {@link #CONTROL_AE_PRIORITY_MODE_OFF OFF}
  • + *
  • {@link #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY SENSOR_SENSITIVITY_PRIORITY}
  • + *
  • {@link #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY SENSOR_EXPOSURE_TIME_PRIORITY}
  • + *
+ * + *

Optional - The value for this key may be {@code null} on some devices.

+ * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see #CONTROL_AE_PRIORITY_MODE_OFF + * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY + * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final Key CONTROL_AE_PRIORITY_MODE = + new Key("android.control.aePriorityMode", int.class); + /** *

Operation mode for edge * enhancement.

@@ -3527,7 +3570,9 @@ public final class CaptureRequest extends CameraMetadata> * duration exposed to the nearest possible value (rather than expose longer). * The final exposure time used will be available in the output capture result.

*

This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to - * OFF; otherwise the auto-exposure algorithm will override this value.

+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the + * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_EXPOSURE_TIME_PRIORITY, this + * control will be effective and not controlled by the auto-exposure algorithm.

*

Units: Nanoseconds

*

Range of valid values:
* {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}

@@ -3537,6 +3582,7 @@ public final class CaptureRequest extends CameraMetadata> * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

* * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE @@ -3645,7 +3691,9 @@ public final class CaptureRequest extends CameraMetadata> * value. The final sensitivity used will be available in the * output capture result.

*

This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to - * OFF; otherwise the auto-exposure algorithm will override this value.

+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the + * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_SENSITIVITY_PRIORITY, this + * control will be effective and not controlled by the auto-exposure algorithm.

*

Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor @@ -3661,6 +3709,7 @@ public final class CaptureRequest extends CameraMetadata> * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

* * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 674fc662aeacd98fa617de0eca04c613ae191856..a52be973f564c82c78e29928e312ec03a8c5cbeb 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -807,7 +807,9 @@ public class CaptureResult extends CameraMetadata> { * application's selected exposure time, sensor sensitivity, * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and - * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is + * enabled, the relevant priority CaptureRequest settings will not be overridden. + * See {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for more details. If one of the FLASH modes * is selected, the camera device's flash unit controls are * also overridden.

*

The FLASH modes are only available if the camera device @@ -841,6 +843,7 @@ public class CaptureResult extends CameraMetadata> { * * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#FLASH_INFO_AVAILABLE * @see CaptureRequest#FLASH_MODE @@ -2952,6 +2955,46 @@ public class CaptureResult extends CameraMetadata> { public static final Key CONTROL_ZOOM_METHOD = new Key("android.control.zoomMethod", int.class); + /** + *

Turn on AE priority mode.

+ *

This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is + * AUTO and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to one of its + * ON modes, with the exception of ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.

+ *

When a priority mode is enabled, the camera device's + * auto-exposure routine will maintain the application's + * selected parameters relevant to the priority mode while overriding + * the remaining exposure parameters + * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and + * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). For example, if + * SENSOR_SENSITIVITY_PRIORITY mode is enabled, the camera device will + * maintain the application-selected {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} + * while adjusting {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} + * and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}. The overridden fields for a + * given capture will be available in its CaptureResult.

+ *

Possible values:

+ *
    + *
  • {@link #CONTROL_AE_PRIORITY_MODE_OFF OFF}
  • + *
  • {@link #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY SENSOR_SENSITIVITY_PRIORITY}
  • + *
  • {@link #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY SENSOR_EXPOSURE_TIME_PRIORITY}
  • + *
+ * + *

Optional - The value for this key may be {@code null} on some devices.

+ * + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_MODE + * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_SENSITIVITY + * @see #CONTROL_AE_PRIORITY_MODE_OFF + * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY + * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_AE_PRIORITY) + public static final Key CONTROL_AE_PRIORITY_MODE = + new Key("android.control.aePriorityMode", int.class); + /** *

Operation mode for edge * enhancement.

@@ -4237,7 +4280,9 @@ public class CaptureResult extends CameraMetadata> { * duration exposed to the nearest possible value (rather than expose longer). * The final exposure time used will be available in the output capture result.

*

This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to - * OFF; otherwise the auto-exposure algorithm will override this value.

+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the + * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_EXPOSURE_TIME_PRIORITY, this + * control will be effective and not controlled by the auto-exposure algorithm.

*

Units: Nanoseconds

*

Range of valid values:
* {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}

@@ -4247,6 +4292,7 @@ public class CaptureResult extends CameraMetadata> { * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

* * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE @@ -4355,7 +4401,9 @@ public class CaptureResult extends CameraMetadata> { * value. The final sensitivity used will be available in the * output capture result.

*

This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to - * OFF; otherwise the auto-exposure algorithm will override this value.

+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the + * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_SENSITIVITY_PRIORITY, this + * control will be effective and not controlled by the auto-exposure algorithm.

*

Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor @@ -4371,6 +4419,7 @@ public class CaptureResult extends CameraMetadata> { * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

* * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE * @see CaptureRequest#CONTROL_MODE * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 84072585d7f027a5fd7521d83404b1f39d28c6c9..ea70abb55b48b5050cd622c8ea8d768003e22555 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -18,6 +18,7 @@ package android.hardware.camera2.impl; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; @@ -41,12 +42,15 @@ import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.ICameraOfflineSession; import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.params.SharedSessionConfiguration; +import android.hardware.camera2.params.SharedSessionConfiguration.SharedOutputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SubmitInfo; import android.hardware.camera2.utils.SurfaceUtils; @@ -188,6 +192,8 @@ public class CameraDeviceImpl extends CameraDevice private ExecutorService mOfflineSwitchService; private CameraOfflineSessionImpl mOfflineSessionImpl; + private boolean mSharedMode; + private boolean mIsPrimaryClient; // Runnables for all state transitions, except error, which needs the // error code argument @@ -208,6 +214,25 @@ public class CameraDeviceImpl extends CameraDevice } }; + private final Runnable mCallOnOpenedInSharedMode = new Runnable() { + @Override + public void run() { + if (!Flags.cameraMultiClient()) { + return; + } + StateCallbackKK sessionCallback = null; + synchronized (mInterfaceLock) { + if (mRemoteDevice == null) return; // Camera already closed + + sessionCallback = mSessionStateCallback; + } + if (sessionCallback != null) { + sessionCallback.onOpenedInSharedMode(CameraDeviceImpl.this, mIsPrimaryClient); + } + mDeviceCallback.onOpenedInSharedMode(CameraDeviceImpl.this, mIsPrimaryClient); + } + }; + private final Runnable mCallOnUnconfigured = new Runnable() { @Override public void run() { @@ -322,6 +347,32 @@ public class CameraDeviceImpl extends CameraDevice } }); } + + public void onOpenedInSharedMode(@NonNull CameraDevice camera, boolean primaryClient) { + if (!Flags.cameraMultiClient()) { + return; + } + mClientExecutor.execute(new Runnable() { + @Override + public void run() { + mClientStateCallback.onOpenedInSharedMode(camera, primaryClient); + } + }); + } + + public void onClientSharedAccessPriorityChanged(@NonNull CameraDevice camera, + boolean primaryClient) { + if (!Flags.cameraMultiClient()) { + return; + } + mClientExecutor.execute(new Runnable() { + @Override + public void run() { + mClientStateCallback.onClientSharedAccessPriorityChanged(camera, primaryClient); + } + }); + } + @Override public void onOpened(@NonNull CameraDevice camera) { mClientExecutor.execute(new Runnable() { @@ -358,7 +409,8 @@ public class CameraDeviceImpl extends CameraDevice @NonNull CameraManager manager, int appTargetSdkVersion, Context ctx, - @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) { + @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup, + boolean sharedMode) { if (cameraId == null || callback == null || executor == null || characteristics == null || manager == null) { throw new IllegalArgumentException("Null argument given"); @@ -375,6 +427,7 @@ public class CameraDeviceImpl extends CameraDevice mAppTargetSdkVersion = appTargetSdkVersion; mContext = ctx; mCameraDeviceSetup = cameraDeviceSetup; + mSharedMode = sharedMode; final int MAX_TAG_LEN = 23; String tag = String.format("CameraDevice-JV-%s", mCameraId); @@ -438,7 +491,12 @@ public class CameraDeviceImpl extends CameraDevice } } - mDeviceExecutor.execute(mCallOnOpened); + if (Flags.cameraMultiClient() && mSharedMode) { + mIsPrimaryClient = mRemoteDevice.isPrimaryClient(); + mDeviceExecutor.execute(mCallOnOpenedInSharedMode); + } else { + mDeviceExecutor.execute(mCallOnOpened); + } mDeviceExecutor.execute(mCallOnUnconfigured); mRemoteDeviceInit = true; @@ -576,7 +634,11 @@ public class CameraDeviceImpl extends CameraDevice stopRepeating(); try { - waitUntilIdle(); + // if device is opened in shared mode, there can be multiple clients accessing the + // camera device. So do not wait for idle if the device is opened in shared mode. + if (!mSharedMode) { + waitUntilIdle(); + } mRemoteDevice.beginConfigure(); @@ -764,6 +826,54 @@ public class CameraDeviceImpl extends CameraDevice checkAndWrapHandler(handler), operatingMode, /*sessionParams*/ null); } + private boolean checkSharedOutputConfiguration(OutputConfiguration outConfig) { + if (!Flags.cameraMultiClient()) { + return false; + } + SharedSessionConfiguration sharedSessionConfiguration = + mCharacteristics.get(CameraCharacteristics.SHARED_SESSION_CONFIGURATION); + if (sharedSessionConfiguration == null) { + return false; + } + + List sharedConfigs = + sharedSessionConfiguration.getOutputStreamsInformation(); + for (SharedOutputConfiguration sharedConfig : sharedConfigs) { + if (outConfig.getConfiguredSize().equals(sharedConfig.getSize()) + && (outConfig.getConfiguredFormat() == sharedConfig.getFormat()) + && (outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE) + && (outConfig.getSurfaceType() == sharedConfig.getSurfaceType()) + && (outConfig.getMirrorMode() == sharedConfig.getMirrorMode()) + && (outConfig.getUsage() == sharedConfig.getUsage()) + && (outConfig.isReadoutTimestampEnabled() + == sharedConfig.isReadoutTimestampEnabled()) + && (outConfig.getTimestampBase() == sharedConfig.getTimestampBase()) + && (outConfig.getStreamUseCase() == sharedConfig.getStreamUseCase()) + && (outConfig.getColorSpace().equals( + sharedSessionConfiguration.getColorSpace())) + && (outConfig.getDynamicRangeProfile() + == DynamicRangeProfiles.STANDARD) + && (outConfig.getConfiguredDataspace() == sharedConfig.getDataspace()) + && (Objects.equals(outConfig.getPhysicalCameraId(), + sharedConfig.getPhysicalCameraId())) + && (outConfig.getSensorPixelModes().isEmpty()) + && (!outConfig.isShared())) { + //Found valid config, return true + return true; + } + } + return false; + } + + private boolean checkSharedSessionConfiguration(List outputConfigs) { + for (OutputConfiguration out : outputConfigs) { + if (!checkSharedOutputConfiguration(out)) { + return false; + } + } + return true; + } + @Override public void createCaptureSession(SessionConfiguration config) throws CameraAccessException { @@ -778,6 +888,14 @@ public class CameraDeviceImpl extends CameraDevice if (config.getExecutor() == null) { throw new IllegalArgumentException("Invalid executor"); } + if (mSharedMode) { + if (config.getSessionType() != SessionConfiguration.SESSION_SHARED) { + throw new IllegalArgumentException("Invalid session type"); + } + if (!checkSharedSessionConfiguration(outputConfigs)) { + throw new IllegalArgumentException("Invalid output configurations"); + } + } createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs, config.getStateCallback(), config.getExecutor(), config.getSessionType(), config.getSessionParameters()); @@ -801,6 +919,11 @@ public class CameraDeviceImpl extends CameraDevice throw new IllegalArgumentException("Constrained high speed session doesn't support" + " input configuration yet."); } + boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE); + if (isSharedSession && inputConfig != null) { + throw new IllegalArgumentException("Shared capture session doesn't support" + + " input configuration yet."); + } if (mCurrentExtensionSession != null) { mCurrentExtensionSession.commitStats(); @@ -860,6 +983,10 @@ public class CameraDeviceImpl extends CameraDevice newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++, callback, executor, this, mDeviceExecutor, configureSuccess, mCharacteristics); + } else if (isSharedSession) { + newSession = new CameraSharedCaptureSessionImpl(mNextSessionId++, + callback, executor, this, mDeviceExecutor, configureSuccess, + mIsPrimaryClient); } else { newSession = new CameraCaptureSessionImpl(mNextSessionId++, input, callback, executor, this, mDeviceExecutor, configureSuccess); @@ -1882,6 +2009,40 @@ public class CameraDeviceImpl extends CameraDevice } } + /** + * Callback when client access priorities change when camera is opened in shared mode. + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void onClientSharedAccessPriorityChanged(boolean primaryClient) { + if (DEBUG) { + Log.d(TAG, String.format( + "onClientSharedAccessPriorityChanged received, primary client = " + + primaryClient)); + } + synchronized (mInterfaceLock) { + if (mRemoteDevice == null && mRemoteDeviceInit) { + return; // Camera already closed, user is not interested in this callback anymore. + } + final long ident = Binder.clearCallingIdentity(); + try { + mDeviceExecutor.execute(obtainRunnable( + CameraDeviceImpl::notifyClientSharedAccessPriorityChanged, this, + primaryClient).recycleOnUse()); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + private void notifyClientSharedAccessPriorityChanged(boolean primaryClient) { + if (!CameraDeviceImpl.this.isClosed()) { + mIsPrimaryClient = primaryClient; + mDeviceCallback.onClientSharedAccessPriorityChanged(CameraDeviceImpl.this, + primaryClient); + } + } + public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { if (DEBUG) { Log.d(TAG, String.format( @@ -2446,6 +2607,12 @@ public class CameraDeviceImpl extends CameraDevice } } + @Override + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void onClientSharedAccessPriorityChanged(boolean primaryClient) { + CameraDeviceImpl.this.onClientSharedAccessPriorityChanged(primaryClient); + } + @Override public void onPrepared(int streamId) { final OutputConfiguration output; diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 1cc085658bfa30b8f408e3cd3b261b8361ea4d38..c0a5928a369bd0eb60b20d7a93398a110e3becf6 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -63,6 +63,7 @@ import android.hardware.camera2.params.OisSample; import android.hardware.camera2.params.RecommendedStreamConfiguration; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.ReprocessFormatsMap; +import android.hardware.camera2.params.SharedSessionConfiguration; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -866,6 +867,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getLensIntrinsicSamples(); } }); + sGetCommandMap.put( + CameraCharacteristics.SHARED_SESSION_CONFIGURATION.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public T getValue(CameraMetadataNative metadata, Key key) { + return (T) metadata.getSharedSessionConfiguration(); + } + }); } private int[] getAvailableFormats() { @@ -1658,6 +1668,22 @@ public class CameraMetadataNative implements Parcelable { listHighResolution); } + private SharedSessionConfiguration getSharedSessionConfiguration() { + if (!Flags.cameraMultiClient()) { + return null; + } + Integer sharedSessionColorSpace = getBase( + CameraCharacteristics.SHARED_SESSION_COLOR_SPACE); + long[] sharedOutputConfigurations = getBase( + CameraCharacteristics.SHARED_SESSION_OUTPUT_CONFIGURATIONS); + + if ((sharedSessionColorSpace == null) || (sharedOutputConfigurations == null)) { + return null; + } + + return new SharedSessionConfiguration(sharedSessionColorSpace, sharedOutputConfigurations); + } + private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java index eb2ff88ec1b222e85cd31db39f783b1fb888282c..1769c4638805af9bf21edbfef3846c70f50a1369 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -16,11 +16,13 @@ package android.hardware.camera2.impl; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.FlaggedApi; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback; import android.hardware.camera2.CaptureFailure; @@ -40,15 +42,15 @@ import android.util.Range; import android.util.SparseArray; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Executor; - -import static com.android.internal.util.Preconditions.*; +import java.util.concurrent.atomic.AtomicBoolean; public class CameraOfflineSessionImpl extends CameraOfflineSession implements IBinder.DeathRecipient { @@ -175,6 +177,12 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession lastFrameNumber); } + @Override + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + public void onClientSharedAccessPriorityChanged(boolean primaryClient) { + Log.v(TAG, "onClientSharedAccessPriorityChanged primaryClient = " + primaryClient); + } + @Override public void onDeviceIdle() { synchronized(mInterfaceLock) { diff --git a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a1f31c0ced5ed69f696ce757f643cde9d778b917 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.camera2.impl; + +import android.annotation.FlaggedApi; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraSharedCaptureSession; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.OutputConfiguration; +import android.os.ConditionVariable; +import android.os.Handler; +import android.view.Surface; + +import com.android.internal.camera.flags.Flags; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Standard implementation of CameraSharedCaptureSession. + * + *

+ * Mostly just forwards calls to an instance of CameraCaptureSessionImpl, + * but implements the few necessary behavior changes and additional methods required + * for the shared session mode. + *

+ */ +@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) +public class CameraSharedCaptureSessionImpl + extends CameraSharedCaptureSession implements CameraCaptureSessionCore { + private static final String TAG = "CameraSharedCaptureSessionImpl"; + private final CameraCaptureSessionImpl mSessionImpl; + private final ConditionVariable mInitialized = new ConditionVariable(); + private boolean mIsPrimary; + + /** + * Create a new CameraCaptureSession. + */ + CameraSharedCaptureSessionImpl(int id, + CameraCaptureSession.StateCallback callback, Executor stateExecutor, + android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, + Executor deviceStateExecutor, boolean configureSuccess, boolean isPrimary) { + CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback); + mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback, + stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess); + mIsPrimary = isPrimary; + mInitialized.open(); + } + + @Override + public int startStreaming(List surfaces, Executor executor, CaptureCallback listener) + throws CameraAccessException { + // Todo: Need to add implementation. + return 0; + } + + @Override + public void stopStreaming() throws CameraAccessException { + // Todo: Need to add implementation. + } + + @Override + public void close() { + mSessionImpl.close(); + } + + @Override + public Surface getInputSurface() { + return null; + } + + @Override + public boolean isReprocessable() { + return false; + } + + @Override + public void abortCaptures() throws CameraAccessException { + if (mIsPrimary) { + mSessionImpl.abortCaptures(); + } + } + + @Override + public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener, + Handler handler) throws CameraAccessException { + if (mIsPrimary) { + return mSessionImpl.setRepeatingRequest(request, listener, handler); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override + public void stopRepeating() throws CameraAccessException { + if (mIsPrimary) { + mSessionImpl.stopRepeating(); + } + } + + @Override + public int capture(CaptureRequest request, CaptureCallback listener, Handler handler) + throws CameraAccessException { + if (mIsPrimary) { + return mSessionImpl.capture(request, listener, handler); + } + throw new UnsupportedOperationException("Shared capture session only supports this method" + + " for primary clients"); + } + + @Override + public void tearDown(Surface surface) throws CameraAccessException { + mSessionImpl.tearDown(surface); + } + + @Override + public CameraDevice getDevice() { + return mSessionImpl.getDevice(); + } + + @Override + public boolean isAborting() { + return mSessionImpl.isAborting(); + } + + @Override + public CameraDeviceImpl.StateCallbackKK getDeviceStateCallback() { + return mSessionImpl.getDeviceStateCallback(); + } + + @Override + public void replaceSessionClose() { + mSessionImpl.replaceSessionClose(); + } + + @Override + public int setRepeatingBurst(List requests, CaptureCallback listener, + Handler handler) throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session doesn't support" + + " this method"); + } + + @Override + public int captureBurst(List requests, CaptureCallback listener, + Handler handler) throws CameraAccessException { + throw new UnsupportedOperationException("Shared Capture session doesn't support" + + " this method"); + } + + @Override + public void updateOutputConfiguration(OutputConfiguration config) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session doesn't support" + + " this method"); + } + + @Override + public void finalizeOutputConfigurations(List deferredOutputConfigs) + throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session doesn't support" + + " this method"); + } + + @Override + public void prepare(Surface surface) throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session doesn't support" + + " this method"); + } + + @Override + public void prepare(int maxCount, Surface surface) throws CameraAccessException { + throw new UnsupportedOperationException("Shared capture session doesn't support" + + " this method"); + } + + @Override + public void closeWithoutDraining() { + throw new UnsupportedOperationException("Shared capture session doesn't support" + + " this method"); + } + + private class WrapperCallback extends StateCallback { + private final StateCallback mCallback; + + WrapperCallback(StateCallback callback) { + mCallback = callback; + } + + @Override + public void onConfigured(CameraCaptureSession session) { + mInitialized.block(); + mCallback.onConfigured(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + mInitialized.block(); + mCallback.onConfigureFailed(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onReady(CameraCaptureSession session) { + mCallback.onReady(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onActive(CameraCaptureSession session) { + mCallback.onActive(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onCaptureQueueEmpty(CameraCaptureSession session) { + mCallback.onCaptureQueueEmpty(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onClosed(CameraCaptureSession session) { + mCallback.onClosed(CameraSharedCaptureSessionImpl.this); + } + + @Override + public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { + mCallback.onSurfacePrepared(CameraSharedCaptureSessionImpl.this, + surface); + } + } +} diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index aec2cff61d991797c3b46b5c3b0337bfb40d420a..831c75ec5d33c3df86211d0445433de1d24b250b 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -301,4 +301,16 @@ public class ICameraDeviceUserWrapper { } } + /** + * API to check if the client is primary client when camera device is opened in shared mode. + */ + public boolean isPrimaryClient() throws CameraAccessException { + try { + return mRemoteDevice.isPrimaryClient(); + } catch (ServiceSpecificException e) { + throw ExceptionUtils.throwAsPublicException(e); + } catch (RemoteException e) { + throw ExceptionUtils.throwAsPublicException(e); + } + } } diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index d38be9b7b694ca58fffbae5d52f1ecd4b5dd4765..e12c46322d8c78a256c345e30ded52b9798e60e7 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -29,6 +29,7 @@ import android.annotation.TestApi; import android.graphics.ColorSpace; import android.graphics.ImageFormat; import android.graphics.ImageFormat.Format; +import android.hardware.DataSpace.NamedDataSpace; import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer.Usage; import android.hardware.camera2.CameraCaptureSession; @@ -1728,6 +1729,79 @@ public final class OutputConfiguration implements Parcelable { return mConfiguredSize; } + /** + * Get the configured format associated with this {@link OutputConfiguration}. + * + * @return {@link android.graphics.ImageFormat#Format} associated with this + * {@link OutputConfiguration}. + * + * @hide + */ + public @Format int getConfiguredFormat() { + return mConfiguredFormat; + } + + /** + * Get the usage flag associated with this {@link OutputConfiguration}. + * + * @return {@link HardwareBuffer#Usage} associated with this {@link OutputConfiguration}. + * + * @hide + */ + public @Usage long getUsage() { + return mUsage; + } + + /** + * Get the surface type associated with this {@link OutputConfiguration}. + * + * @return The surface type associated with this {@link OutputConfiguration}. + * + * @see #SURFACE_TYPE_SURFACE_VIEW + * @see #SURFACE_TYPE_SURFACE_TEXTURE + * @see #SURFACE_TYPE_MEDIA_RECORDER + * @see #SURFACE_TYPE_MEDIA_CODEC + * @see #SURFACE_TYPE_IMAGE_READER + * @see #SURFACE_TYPE_UNKNOWN + * @hide + */ + public int getSurfaceType() { + return mSurfaceType; + } + + /** + * Get the sensor pixel modes associated with this {@link OutputConfiguration}. + * + * @return List of {@link #SensorPixelMode} associated with this {@link OutputConfiguration}. + * + * @hide + */ + public @NonNull List getSensorPixelModes() { + return mSensorPixelModesUsed; + } + + /** + * Get the sharing mode associated with this {@link OutputConfiguration}. + * + * @return true if surface sharing is enabled with this {@link OutputConfiguration}. + * + * @hide + */ + public boolean isShared() { + return mIsShared; + } + + /** + * Get the dataspace associated with this {@link OutputConfiguration}. + * + * @return {@link Dataspace#NamedDataSpace} for this {@link OutputConfiguration}. + * + * @hide + */ + public @NamedDataSpace int getConfiguredDataspace() { + return mConfiguredDataspace; + } + /** * Get the physical camera ID associated with this {@link OutputConfiguration}. * diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 50c6b5b8b99552c2631972a6ae228ef22a42a097..82aa64b1474c18fef69bc0b6a37f0772b69a4e84 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.graphics.ColorSpace; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; @@ -77,6 +78,19 @@ public final class SessionConfiguration implements Parcelable { public static final int SESSION_HIGH_SPEED = CameraDevice.SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED; + /** + * A shared session type containing instances of {@link OutputConfiguration} from a set of + * predefined stream configurations. A shared session can be shared among multiple clients. + * Shared session does not have any {@link InputConfiguration} as it does not support + * reprocessable sessions. + * + * @see CameraDevice#createCaptureSession(SessionConfiguration) + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) + @SystemApi + public static final int SESSION_SHARED = CameraDevice.SESSION_OPERATION_MODE_SHARED; + /** * First vendor-specific session mode * @hide diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..cdcc92ce44046603afdd8385a6cd92b531a8e182 --- /dev/null +++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.graphics.ColorSpace; +import android.graphics.ImageFormat.Format; +import android.hardware.DataSpace.NamedDataSpace; +import android.hardware.HardwareBuffer.Usage; +import android.hardware.camera2.params.OutputConfiguration.MirrorMode; +import android.hardware.camera2.params.OutputConfiguration.StreamUseCase; +import android.hardware.camera2.params.OutputConfiguration.TimestampBase; +import android.util.Log; +import android.util.Size; + +import com.android.internal.camera.flags.Flags; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Immutable class to store the shared session configuration + * {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION} to set up + * {@link android.view.Surface Surfaces} for creating a + * {@link android.hardware.camera2.CameraSharedCaptureSession capture session} using + * {@link android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)} and + * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_SHARED + * shared capture session} when camera has been opened in shared mode using + * {@link #openSharedCamera(String, Executor, StateCallback)}. + * + *

This is the authoritative list for all output configurations that are supported by a camera + * device when opened in shared mode.

+ * + *

An instance of this object is available from {@link CameraCharacteristics} using + * the {@link CameraCharacteristics#SHARED_SESSION_CONFIGURATION} key and the + * {@link CameraCharacteristics#get} method.

+ * + *
{@code
+ * CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ * StreamConfigurationMap configs = characteristics.get(
+ *         CameraCharacteristics.SHARED_SESSION_CONFIGURATION);
+ * }
+ * + * @see CameraCharacteristics#SHARED_SESSION_CONFIGURATION + * @see CameraDevice#createCaptureSession(SessionConfiguration) + * @see SessionConfiguration#SESSION_SHARED + * @see CameraManager#openSharedCamera(String, Executor, StateCallback) + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_CAMERA_MULTI_CLIENT) +public final class SharedSessionConfiguration { + private static final String TAG = "SharedSessionConfiguration"; + // Metadata android.info.availableSharedOutputConfigurations has list of shared output + // configurations. Each output configuration has minimum of 11 entries of size long + // followed by the physical camera id if present. + // See android.info.availableSharedOutputConfigurations for details. + private static final int SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES = 11; + /** + * Immutable class to store shared output stream information. + */ + public static final class SharedOutputConfiguration { + private final int mSurfaceType; + private final Size mSize; + private final int mFormat; + private final int mDataspace; + private final long mStreamUseCase; + private String mPhysicalCameraId; + private final long mUsage; + private int mTimestampBase; + private int mMirrorMode; + private boolean mReadoutTimestampEnabled; + + /** + * Create a new {@link SharedOutputConfiguration}. + * + * @param surfaceType Surface Type for this output configuration. + * @param sz Size for this output configuration. + * @param format {@link android.graphics.ImageFormat#Format} associated with this + * {@link OutputConfiguration}. + * @param mirrorMode {@link OutputConfiguration#MirrorMode} for this output configuration. + * @param readoutTimeStampEnabled Flag indicating whether readout timestamp is enabled + * for this output configuration. + * @param timestampBase {@link OutputConfiguration#TimestampBase} for this output + * configuration. + * @param dataspace {@link Dataspace#NamedDataSpace} for this output configuration. + * @param usage {@link HardwareBuffer#Usage} for this output configuration. + * @param streamUseCase {@link OutputConfiguration#StreamUseCase} for this output + * configuration. + * @param physicalCamId Physical Camera Id for this output configuration. + * + * @hide + */ + public SharedOutputConfiguration(int surfaceType, @NonNull Size sz, @Format int format, + @MirrorMode int mirrorMode, boolean readoutTimeStampEnabled, + @TimestampBase int timestampBase, @NamedDataSpace int dataspace, @Usage long usage, + @StreamUseCase long streamUseCase, @Nullable String physicalCamId) { + mSurfaceType = surfaceType; + mSize = sz; + mFormat = format; + mMirrorMode = mirrorMode; + mReadoutTimestampEnabled = readoutTimeStampEnabled; + mTimestampBase = timestampBase; + mDataspace = dataspace; + mUsage = usage; + mStreamUseCase = streamUseCase; + mPhysicalCameraId = physicalCamId; + } + + /** + * Returns the surface type configured for the shared output configuration. + * @return SURFACE_TYPE_UNKNOWN = -1 + * SURFACE_TYPE_SURFACE_VIEW = 0 + * SURFACE_TYPE_SURFACE_TEXTURE = 1 + * SURFACE_TYPE_MEDIA_RECORDER = 2 + * SURFACE_TYPE_MEDIA_CODEC = 3 + * SURFACE_TYPE_IMAGE_READER = 4 + */ + public int getSurfaceType() { + return mSurfaceType; + } + + /** + * Returns the format of the shared output configuration. + * @return format The format of the configured output. This must be one of the + * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} + * constants. Note that not all formats are supported by the camera device. + */ + public @Format int getFormat() { + return mFormat; + } + + /** + * Returns the configured size for the shared output configuration. + * @return surfaceSize Size for the shared output configuration + * + */ + public @NonNull Size getSize() { + return mSize; + } + + /** + * Return datatspace configured for the shared output configuration. + * + * @return {@link Dataspace#NamedDataSpace} configured for shared session + */ + public @NamedDataSpace int getDataspace() { + return mDataspace; + } + + /** + * Get the mirroring mode configured for the shared output configuration. + * + * @return {@link OutputConfiguration#MirrorMode} configured for the shared session + */ + public @MirrorMode int getMirrorMode() { + return mMirrorMode; + } + + /** + * Get the stream use case configured for the shared output configuration. + * + * @return {@link OutputConfiguration#StreamUseCase} configured for the shared session + */ + public @StreamUseCase long getStreamUseCase() { + return mStreamUseCase; + } + + /** + * Get the timestamp base configured for the shared output configuration. + * + * @return {@link OutputConfiguration#TimestampBase} configured for the shared session + */ + public @TimestampBase int getTimestampBase() { + return mTimestampBase; + } + + /** Whether readout timestamp is used for this shared output configuration. + * + */ + public boolean isReadoutTimestampEnabled() { + return mReadoutTimestampEnabled; + } + + /** Returns the usage if set for this shared output configuration. + * + * @return {@link HardwareBuffer#Usage} flags if set for shared output configuration with + * the ImageReader output surface. + */ + public @Usage long getUsage() { + return mUsage; + } + + public @Nullable String getPhysicalCameraId() { + return mPhysicalCameraId; + } + } + + /** + * Create a new {@link SharedSessionConfiguration}. + * + *

The array parameters ownership is passed to this object after creation; do not + * write to them after this constructor is invoked.

+ * + * @param sharedColorSpace the colorspace to be used for the shared output configurations. + * @param sharedOutputConfigurations a non-{@code null} array of metadata + * android.info.availableSharedOutputConfigurations + * + * @hide + */ + public SharedSessionConfiguration(int sharedColorSpace, + @NonNull long[] sharedOutputConfigurations) { + mColorSpace = sharedColorSpace; + byte physicalCameraIdLen; + int surfaceType, width, height, format, mirrorMode, timestampBase, dataspace; + long usage, streamUseCase; + boolean isReadOutTimestampEnabled; + // Parse metadata android.info.availableSharedOutputConfigurations which contains + // list of shared output configurations. + int numOfEntries = sharedOutputConfigurations.length; + int i = 0; + while (numOfEntries >= SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES) { + surfaceType = (int) sharedOutputConfigurations[i]; + width = (int) sharedOutputConfigurations[i + 1]; + height = (int) sharedOutputConfigurations[i + 2]; + format = (int) sharedOutputConfigurations[i + 3]; + mirrorMode = (int) sharedOutputConfigurations[i + 4]; + isReadOutTimestampEnabled = (sharedOutputConfigurations[i + 5] != 0); + timestampBase = (int) sharedOutputConfigurations[i + 6]; + dataspace = (int) sharedOutputConfigurations[i + 7]; + usage = sharedOutputConfigurations[i + 8]; + streamUseCase = sharedOutputConfigurations[i + 9]; + physicalCameraIdLen = (byte) sharedOutputConfigurations[i + 10]; + numOfEntries -= SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES; + i += SharedSessionConfiguration.SHARED_OUTPUT_CONFIG_NUM_OF_ENTRIES; + if (numOfEntries < physicalCameraIdLen) { + Log.e(TAG, "Number of remaining data in shared configuration is less than" + + " physical camera id length . Malformed metadata" + + " android.info.availableSharedOutputConfigurations."); + break; + } + StringBuilder physicalCameraId = new StringBuilder(); + long asciiValue; + for (int j = 0; j < physicalCameraIdLen; j++) { + asciiValue = sharedOutputConfigurations[i + j]; + if (asciiValue == 0) { // Check for null terminator + break; + } + physicalCameraId.append((char) asciiValue); + } + SharedOutputConfiguration outputInfo; + outputInfo = new SharedOutputConfiguration(surfaceType, new Size(width, height), + format, mirrorMode, isReadOutTimestampEnabled, timestampBase, + dataspace, usage, streamUseCase, physicalCameraId.toString()); + mOutputStreamConfigurations.add(outputInfo); + i += physicalCameraIdLen; + numOfEntries -= physicalCameraIdLen; + } + if (numOfEntries != 0) { + Log.e(TAG, "Unexpected entries left in shared output configuration." + + " Malformed metadata android.info.availableSharedOutputConfigurations."); + } + } + + /** + * Return the shared session color space which is configured. + * + * @return the shared session color space + */ + @SuppressLint("MethodNameUnits") + public @Nullable ColorSpace getColorSpace() { + if (mColorSpace != ColorSpaceProfiles.UNSPECIFIED) { + return ColorSpace.get(ColorSpace.Named.values()[mColorSpace]); + } else { + return null; + } + } + /** + * Get information about each shared output configuarion in the shared session. + * + * @return Non-modifiable list of output configuration. + * + */ + public @NonNull List getOutputStreamsInformation() { + return Collections.unmodifiableList(mOutputStreamConfigurations); + } + + private int mColorSpace; + private final ArrayList mOutputStreamConfigurations = + new ArrayList(); +} + diff --git a/core/java/android/hardware/contexthub/HubDiscoveryInfo.java b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..581040dbfa5663a816acfa892e349abfe0d6a184 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubDiscoveryInfo.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.hardware.location.ContextHubManager; + +/** + * Class that represents the result of from an hub endpoint discovery. + * + *

The type is returned from an endpoint discovery query via {@link + * ContextHubManager#findEndpoints}. + * + *

Application may use the values {@link #getHubEndpointInfo} to retrieve the {@link + * HubEndpointInfo} that describes the endpoint that matches the query. + * + *

Application may use the values {@link #getHubServiceInfo()} to retrieve the {@link + * HubServiceInfo} that describes the service that matches the query. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public class HubDiscoveryInfo { + @NonNull private final HubEndpointInfo mEndpointInfo; + @Nullable private final HubServiceInfo mServiceInfo; + + /** @hide */ + public HubDiscoveryInfo(@NonNull HubEndpointInfo endpointInfo) { + mEndpointInfo = endpointInfo; + mServiceInfo = null; + } + + /** @hide */ + public HubDiscoveryInfo( + @NonNull HubEndpointInfo endpointInfo, @NonNull HubServiceInfo serviceInfo) { + mEndpointInfo = endpointInfo; + mServiceInfo = serviceInfo; + } + + /** Get the {@link HubEndpointInfo} for the endpoint found. */ + @NonNull + public HubEndpointInfo getHubEndpointInfo() { + return mEndpointInfo; + } + + /** + * Get the {@link HubServiceInfo} for the endpoint found. The value will be null if there is no + * service info specified in the query. + */ + @Nullable + public HubServiceInfo getHubServiceInfo() { + return mServiceInfo; + } +} diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..078b4d4629e0ea441976283037c6813ed8446a50 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.content.Context; +import android.hardware.location.IContextHubService; +import android.hardware.location.IContextHubTransactionCallback; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.GuardedBy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * An object representing an endpoint exposed to ContextHub and VendorHub. The object encapsulates + * the lifecycle and message callbacks for an endpoint. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public class HubEndpoint { + private static final String TAG = "HubEndpoint"; + + private final Object mLock = new Object(); + private final HubEndpointInfo mPendingHubEndpointInfo; + @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback; + @Nullable private final IHubEndpointMessageCallback mMessageCallback; + @NonNull private final Executor mLifecycleCallbackExecutor; + @NonNull private final Executor mMessageCallbackExecutor; + + @GuardedBy("mLock") + private final SparseArray mActiveSessions = new SparseArray<>(); + + private final IContextHubEndpointCallback mServiceCallback = + new IContextHubEndpointCallback.Stub() { + @Override + public void onSessionOpenRequest( + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) + throws RemoteException { + HubEndpointSession activeSession; + synchronized (mLock) { + activeSession = mActiveSessions.get(sessionId); + // TODO(b/378974199): Consider refactor these assertions + if (activeSession != null) { + Log.i( + TAG, + "onSessionOpenComplete: session already exists, id=" + + sessionId); + return; + } + } + + if (mLifecycleCallback != null) { + mLifecycleCallbackExecutor.execute( + () -> + processSessionOpenRequestResult( + sessionId, + initiator, + serviceInfo, + mLifecycleCallback.onSessionOpenRequest( + initiator, serviceInfo))); + } + } + + private void processSessionOpenRequestResult( + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo, + HubEndpointSessionResult result) { + if (result == null) { + throw new IllegalArgumentException( + "HubEndpointSessionResult shouldn't be null."); + } + + if (result.isAccepted()) { + acceptSession(sessionId, initiator, serviceInfo); + } else { + Log.i( + TAG, + "Session " + + sessionId + + " from " + + initiator + + " was rejected, reason=" + + result.getReason()); + rejectSession(sessionId); + } + } + + private void acceptSession( + int sessionId, + HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) { + if (mServiceToken == null || mAssignedHubEndpointInfo == null) { + // No longer registered? + return; + } + + // Retrieve the active session + HubEndpointSession activeSession; + synchronized (mLock) { + activeSession = mActiveSessions.get(sessionId); + // TODO(b/378974199): Consider refactor these assertions + if (activeSession != null) { + Log.e( + TAG, + "onSessionOpenRequestResult: session already exists, id=" + + sessionId); + return; + } + + activeSession = + new HubEndpointSession( + sessionId, + HubEndpoint.this, + mAssignedHubEndpointInfo, + initiator, + serviceInfo); + try { + // oneway call to notify system service that the request is completed + mServiceToken.openSessionRequestComplete(sessionId); + } catch (RemoteException e) { + Log.e(TAG, "onSessionOpenRequestResult: ", e); + return; + } + + mActiveSessions.put(sessionId, activeSession); + } + + // Execute the callback + activeSession.setOpened(); + if (mLifecycleCallback != null) { + final HubEndpointSession finalActiveSession = activeSession; + mLifecycleCallbackExecutor.execute( + () -> mLifecycleCallback.onSessionOpened(finalActiveSession)); + } + } + + private void rejectSession(int sessionId) { + if (mServiceToken == null || mAssignedHubEndpointInfo == null) { + // No longer registered? + return; + } + + try { + mServiceToken.closeSession( + sessionId, + IHubEndpointLifecycleCallback + .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override + public void onSessionOpenComplete(int sessionId) throws RemoteException { + final HubEndpointSession activeSession; + + // Retrieve the active session + synchronized (mLock) { + activeSession = mActiveSessions.get(sessionId); + } + // TODO(b/378974199): Consider refactor these assertions + if (activeSession == null) { + Log.i( + TAG, + "onSessionOpenComplete: no pending session open request? id=" + + sessionId); + return; + } + + // Execute the callback + activeSession.setOpened(); + if (mLifecycleCallback != null) { + mLifecycleCallbackExecutor.execute( + () -> mLifecycleCallback.onSessionOpened(activeSession)); + } + } + + @Override + public void onSessionClosed(int sessionId, int reason) throws RemoteException { + final HubEndpointSession activeSession; + + // Retrieve the active session + synchronized (mLock) { + activeSession = mActiveSessions.get(sessionId); + } + // TODO(b/378974199): Consider refactor these assertions + if (activeSession == null) { + Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId); + return; + } + + // Execute the callback + if (mLifecycleCallback != null) { + mLifecycleCallbackExecutor.execute( + () -> { + mLifecycleCallback.onSessionClosed(activeSession, reason); + + // Remove the session object first to call + activeSession.setClosed(); + synchronized (mLock) { + mActiveSessions.remove(sessionId); + } + }); + } + } + + @Override + public void onMessageReceived(int sessionId, HubMessage message) + throws RemoteException { + final HubEndpointSession activeSession; + + // Retrieve the active session + synchronized (mLock) { + activeSession = mActiveSessions.get(sessionId); + } + if (activeSession == null) { + Log.i(TAG, "onMessageReceived: session not active, id=" + sessionId); + } + + if (activeSession == null || mMessageCallback == null) { + if (message.getDeliveryParams().isResponseRequired()) { + try { + mServiceToken.sendMessageDeliveryStatus( + sessionId, + message.getMessageSequenceNumber(), + ErrorCode.DESTINATION_NOT_FOUND); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + return; + } + + // Execute the callback + mMessageCallbackExecutor.execute( + () -> { + mMessageCallback.onMessageReceived(activeSession, message); + if (message.getDeliveryParams().isResponseRequired()) { + try { + mServiceToken.sendMessageDeliveryStatus( + sessionId, + message.getMessageSequenceNumber(), + ErrorCode.OK); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + }); + } + }; + + /** Binder returned from system service, non-null while registered. */ + @Nullable private IContextHubEndpoint mServiceToken; + + /** HubEndpointInfo with the assigned endpoint id from system service. */ + @Nullable private HubEndpointInfo mAssignedHubEndpointInfo; + + private HubEndpoint( + @NonNull HubEndpointInfo pendingEndpointInfo, + @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback, + @NonNull Executor lifecycleCallbackExecutor, + @Nullable IHubEndpointMessageCallback endpointMessageCallback, + @NonNull Executor messageCallbackExecutor) { + mPendingHubEndpointInfo = pendingEndpointInfo; + + mLifecycleCallback = endpointLifecycleCallback; + mLifecycleCallbackExecutor = lifecycleCallbackExecutor; + mMessageCallback = endpointMessageCallback; + mMessageCallbackExecutor = messageCallbackExecutor; + } + + /** @hide */ + public void register(IContextHubService service) { + // TODO(b/378974199): Consider refactor these assertions + if (mServiceToken != null) { + // Already registered + return; + } + try { + IContextHubEndpoint serviceToken = + service.registerEndpoint(mPendingHubEndpointInfo, mServiceCallback); + mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo(); + mServiceToken = serviceToken; + } catch (RemoteException e) { + Log.e(TAG, "registerEndpoint: failed to register endpoint", e); + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void unregister() { + IContextHubEndpoint serviceToken = mServiceToken; + // TODO(b/378974199): Consider refactor these assertions + if (serviceToken == null) { + // Not yet registered + return; + } + + try { + synchronized (mLock) { + // Don't call HubEndpointSession.close() here. + for (int i = 0; i < mActiveSessions.size(); i++) { + mActiveSessions.get(mActiveSessions.keyAt(i)).setClosed(); + } + mActiveSessions.clear(); + } + mServiceToken.unregister(); + } catch (RemoteException e) { + Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e); + e.rethrowFromSystemServer(); + } finally { + mServiceToken = null; + mAssignedHubEndpointInfo = null; + } + } + + /** @hide */ + public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) { + // TODO(b/378974199): Consider refactor these assertions + if (mServiceToken == null || mAssignedHubEndpointInfo == null) { + // No longer registered? + return; + } + + HubEndpointSession newSession; + try { + // Request system service to assign session id. + int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo); + + // Save the newly created session + synchronized (mLock) { + newSession = + new HubEndpointSession( + sessionId, + HubEndpoint.this, + destinationInfo, + mAssignedHubEndpointInfo, + serviceInfo); + mActiveSessions.put(sessionId, newSession); + } + } catch (RemoteException e) { + // Move this to toString + Log.e(TAG, "openSession: failed to open session to " + destinationInfo, e); + e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public void closeSession(HubEndpointSession session) { + IContextHubEndpoint serviceToken = mServiceToken; + // TODO(b/378974199): Consider refactor these assertions + if (serviceToken == null || mAssignedHubEndpointInfo == null) { + // Not registered + return; + } + + synchronized (mLock) { + if (!mActiveSessions.contains(session.getId())) { + // Already closed? + return; + } + session.setClosed(); + mActiveSessions.remove(session.getId()); + } + + try { + // Oneway notification to system service + serviceToken.closeSession( + session.getId(), + IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED); + } catch (RemoteException e) { + Log.e(TAG, "closeSession: failed to close session " + session, e); + e.rethrowFromSystemServer(); + } + } + + void sendMessage( + HubEndpointSession session, + HubMessage message, + @Nullable IContextHubTransactionCallback transactionCallback) { + IContextHubEndpoint serviceToken = mServiceToken; + if (serviceToken == null) { + // Not registered + return; + } + + try { + serviceToken.sendMessage(session.getId(), message, transactionCallback); + } catch (RemoteException e) { + Log.e(TAG, "sendMessage: failed to send message session=" + session, e); + e.rethrowFromSystemServer(); + } + } + + public int getVersion() { + return mPendingHubEndpointInfo.getVersion(); + } + + @Nullable + public String getTag() { + return mPendingHubEndpointInfo.getTag(); + } + + @NonNull + public Collection getServiceInfoCollection() { + return mPendingHubEndpointInfo.getServiceInfoCollection(); + } + + @Nullable + public IHubEndpointLifecycleCallback getLifecycleCallback() { + return mLifecycleCallback; + } + + @Nullable + public IHubEndpointMessageCallback getMessageCallback() { + return mMessageCallback; + } + + /** Builder for a {@link HubEndpoint} object. */ + public static final class Builder { + private final String mPackageName; + + @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback; + + @NonNull private Executor mLifecycleCallbackExecutor; + + @Nullable private IHubEndpointMessageCallback mMessageCallback; + @NonNull private Executor mMessageCallbackExecutor; + + private int mVersion; + @Nullable private String mTag; + + private List mServiceInfos = Collections.emptyList(); + + /** Create a builder for {@link HubEndpoint} */ + public Builder(@NonNull Context context) { + mPackageName = context.getPackageName(); + mVersion = (int) context.getApplicationInfo().longVersionCode; + mLifecycleCallbackExecutor = context.getMainExecutor(); + mMessageCallbackExecutor = context.getMainExecutor(); + } + + /** + * Set the version for the endpoint. Default is 0. + * + * @hide + */ + @NonNull + public Builder setVersion(int version) { + mVersion = version; + return this; + } + + /** + * Set a tag string. The tag can be used to further identify the creator of the endpoint. + * Endpoints created by the same package share the same name but should have different tags. + */ + @NonNull + public Builder setTag(@NonNull String tag) { + mTag = tag; + return this; + } + + /** Attach a callback interface for lifecycle events for this Endpoint */ + @NonNull + public Builder setLifecycleCallback( + @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + mLifecycleCallback = lifecycleCallback; + return this; + } + + /** + * Attach a callback interface for lifecycle events for this Endpoint with a specified + * executor + */ + @NonNull + public Builder setLifecycleCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + mLifecycleCallbackExecutor = executor; + mLifecycleCallback = lifecycleCallback; + return this; + } + + /** Attach a callback interface for message events for this Endpoint */ + @NonNull + public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) { + mMessageCallback = messageCallback; + return this; + } + + /** + * Attach a callback interface for message events for this Endpoint with a specified + * executor + */ + @NonNull + public Builder setMessageCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull IHubEndpointMessageCallback messageCallback) { + mMessageCallbackExecutor = executor; + mMessageCallback = messageCallback; + return this; + } + + /** + * Add a service to the available services from this endpoint. The {@link HubServiceInfo} + * object can be built with {@link HubServiceInfo.Builder}. + */ + @NonNull + public Builder setServiceInfoCollection( + @NonNull Collection hubServiceInfos) { + // Make a copy first + mServiceInfos = new ArrayList<>(hubServiceInfos); + return this; + } + + /** Build the {@link HubEndpoint} object. */ + @NonNull + public HubEndpoint build() { + return new HubEndpoint( + new HubEndpointInfo(mPackageName, mVersion, mTag, mServiceInfos), + mLifecycleCallback, + mLifecycleCallbackExecutor, + mMessageCallback, + mMessageCallbackExecutor); + } + } +} diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.aidl b/core/java/android/hardware/contexthub/HubEndpointInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..025b2b1f685a3904a75e266a75952bbdba6db976 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubEndpointInfo.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +/** @hide */ +parcelable HubEndpointInfo; diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..b1d55239ac438d0d2e389e12da7e7fda7b44fea3 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Parcelable representing an endpoint from ContextHub or VendorHub. + * + *

HubEndpointInfo contains information about an endpoint, including its name, tag and other + * information. A HubEndpointInfo object can be used to accurately identify a specific endpoint. + * Application can use this object to identify and describe an endpoint. + * + *

See: {@link android.hardware.location.ContextHubManager#findEndpoints} for how to retrieve + * {@link HubEndpointInfo} for endpoints on a hub. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubEndpointInfo implements Parcelable { + /** + * A unique identifier for one endpoint. A unique identifier for one endpoint consists of two + * parts: (1) a unique long number for a hub and (2) a long number for the endpoint, unique + * within a hub. This class overrides equality methods and can be used to compare if two + * endpoints are the same. + */ + public static class HubEndpointIdentifier { + private final long mEndpointId; + private final long mHubId; + + /** @hide */ + public HubEndpointIdentifier(long hubId, long endpointId) { + mEndpointId = endpointId; + mHubId = hubId; + } + + /** @hide */ + public HubEndpointIdentifier(android.hardware.contexthub.EndpointId halEndpointId) { + mEndpointId = halEndpointId.id; + mHubId = halEndpointId.hubId; + } + + /** Get the endpoint portion of the identifier. */ + public long getEndpoint() { + return mEndpointId; + } + + /** Get the hub portion of the identifier. */ + public long getHub() { + return mHubId; + } + + /** + * Create an invalid endpoint id, to represent endpoint that are not yet registered with the + * HAL. + * + * @hide + */ + public static HubEndpointIdentifier invalid() { + return new HubEndpointIdentifier( + android.hardware.contexthub.HubInfo.HUB_ID_INVALID, + android.hardware.contexthub.EndpointId.ENDPOINT_ID_INVALID); + } + + @Override + public int hashCode() { + return Objects.hash(mEndpointId, mHubId); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HubEndpointIdentifier other)) { + return false; + } + if (other.mHubId != mHubId) { + return false; + } + return other.mEndpointId == mEndpointId; + } + } + + /** This endpoint is from the Android framework */ + public static final int TYPE_FRAMEWORK = 1; + + /** This endpoint is from an Android app */ + public static final int TYPE_APP = 2; + + /** This endpoint is from an Android native program. */ + public static final int TYPE_NATIVE = 3; + + /** This endpoint is from a nanoapp. */ + public static final int TYPE_NANOAPP = 4; + + /** This endpoint is a generic endpoint served by a hub (not from a nanoapp). */ + public static final int TYPE_HUB_ENDPOINT = 5; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_FRAMEWORK, + TYPE_APP, + TYPE_NATIVE, + TYPE_NANOAPP, + TYPE_HUB_ENDPOINT, + }) + public @interface EndpointType {} + + private final HubEndpointIdentifier mId; + @EndpointType private final int mType; + private final String mName; + private final int mVersion; + @Nullable private final String mTag; + + @NonNull private final List mRequiredPermissions; + @NonNull private final List mHubServiceInfos; + + /** @hide */ + public HubEndpointInfo(android.hardware.contexthub.EndpointInfo endpointInfo) { + mId = new HubEndpointIdentifier(endpointInfo.id.hubId, endpointInfo.id.id); + mType = endpointInfo.type; + mName = endpointInfo.name; + mVersion = endpointInfo.version; + mTag = endpointInfo.tag; + mRequiredPermissions = Arrays.asList(endpointInfo.requiredPermissions); + mHubServiceInfos = new ArrayList<>(endpointInfo.services.length); + for (int i = 0; i < endpointInfo.services.length; i++) { + mHubServiceInfos.set(i, new HubServiceInfo(endpointInfo.services[i])); + } + } + + /** @hide */ + public HubEndpointInfo( + String name, + int version, + @Nullable String tag, + @NonNull List hubServiceInfos) { + mId = HubEndpointIdentifier.invalid(); + mType = TYPE_APP; + mName = name; + mVersion = version; + mTag = tag; + mRequiredPermissions = Collections.emptyList(); + mHubServiceInfos = hubServiceInfos; + } + + private HubEndpointInfo(Parcel in) { + long hubId = in.readLong(); + long endpointId = in.readLong(); + mId = new HubEndpointIdentifier(hubId, endpointId); + mType = in.readInt(); + mName = in.readString(); + mVersion = in.readInt(); + mTag = in.readString(); + mRequiredPermissions = new ArrayList<>(); + in.readStringList(mRequiredPermissions); + mHubServiceInfos = new ArrayList<>(); + in.readTypedList(mHubServiceInfos, HubServiceInfo.CREATOR); + } + + /** Parcel implementation details */ + @Override + public int describeContents() { + int flags = 0; + for (HubServiceInfo serviceInfo : mHubServiceInfos) { + flags |= serviceInfo.describeContents(); + } + return flags; + } + + /** Parcel implementation details */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mId.getHub()); + dest.writeLong(mId.getEndpoint()); + dest.writeInt(mType); + dest.writeString(mName); + dest.writeInt(mVersion); + dest.writeString(mTag); + dest.writeStringList(mRequiredPermissions); + dest.writeTypedList(mHubServiceInfos, flags); + } + + /** Get a unique identifier for this endpoint. */ + @NonNull + public HubEndpointIdentifier getIdentifier() { + return mId; + } + + /** + * Get the type of this endpoint. Application may use this field to get more information about + * who registered this endpoint for diagnostic purposes. + * + *

Type can be one of {@link HubEndpointInfo#TYPE_APP}, {@link + * HubEndpointInfo#TYPE_FRAMEWORK}, {@link HubEndpointInfo#TYPE_NANOAPP}, {@link + * HubEndpointInfo#TYPE_NATIVE} or {@link HubEndpointInfo#TYPE_HUB_ENDPOINT}. + */ + public int getType() { + return mType; + } + + /** Get the human-readable name of this endpoint (for debugging purposes). */ + @NonNull + public String getName() { + return mName; + } + + /** + * Get the version of this endpoint. + * + *

Monotonically increasing version number. The two sides of an endpoint session can use this + * version number to identify the other side and determine compatibility with each other. The + * interpretation of the version number is specific to the implementation of an endpoint. + * + *

The version number should not be used to compare endpoints implementation freshness for + * different endpoint types. + * + *

Depending on type of the endpoint, the following values (and formats) are used: + * + *

    + *
  1. {@link #TYPE_FRAMEWORK}: android.os.Build.VERSION.SDK_INT_FULL + *
  2. {@link #TYPE_APP}: versionCode + *
  3. {@link #TYPE_NATIVE}: unspecified format (supplied by endpoint code) + *
  4. {@link #TYPE_NANOAPP}: nanoapp version, typically following 0xMMmmpppp scheme where MM + * = major version, mm = minor version, pppp = patch version + *
  5. {@link #TYPE_HUB_ENDPOINT}: unspecified format (supplied by endpoint code), following + * nanoapp versioning scheme is recommended + *
+ */ + public int getVersion() { + return mVersion; + } + + /** + * Get the tag that further identifies the submodule that created this endpoint. For example, a + * single application could provide multiple endpoints. These endpoints will share the same + * name, but will have different tags. This tag can be used to identify the submodule within the + * application that provided the endpoint. + */ + @Nullable + public String getTag() { + return mTag; + } + + /** + * Get the list of required permissions in order to talk to this endpoint. + * + *

This list is enforced by the Context Hub Service. The app would need to have the required + * permissions list to open a session with this particular endpoint. Otherwise this will be + * rejected by as permission failures. + * + *

This is mostly for allowing app to check what permission it needs first internally. App + * will need to request permissions grant at runtime if not already granted. See {@link + * android.content.Context#checkPermission} for more details. + * + *

See {@link android.Manifest.permission} for a list of standard Android permissions as + * possible values. + */ + @SuppressLint("RequiresPermission") + @NonNull + public Collection getRequiredPermissions() { + return Collections.unmodifiableList(mRequiredPermissions); + } + + /** + * Get the list of services provided by this endpoint. + * + *

See {@link HubServiceInfo} for more information. + */ + @NonNull + public Collection getServiceInfoCollection() { + return Collections.unmodifiableList(mHubServiceInfos); + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("Endpoint [0x"); + out.append(Long.toHexString(mId.getEndpoint())); + out.append("@ Hub 0x"); + out.append(Long.toHexString(mId.getHub())); + out.append("] Name="); + out.append(mName); + out.append(", Tag="); + out.append(mTag); + return out.toString(); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator<>() { + public HubEndpointInfo createFromParcel(Parcel in) { + return new HubEndpointInfo(in); + } + + public HubEndpointInfo[] newArray(int size) { + return new HubEndpointInfo[size]; + } + }; +} diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java new file mode 100644 index 0000000000000000000000000000000000000000..cf952cbdbfdc54864b9d9c8d18ad9696391ade16 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.ContextHubTransactionHelper; +import android.hardware.location.IContextHubTransactionCallback; +import android.util.CloseGuard; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An object representing a communication session between two different hub endpoints. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public class HubEndpointSession implements AutoCloseable { + private final CloseGuard mCloseGuard = new CloseGuard(); + + private final int mId; + + @NonNull private final HubEndpoint mHubEndpoint; + @NonNull private final HubEndpointInfo mInitiator; + @NonNull private final HubEndpointInfo mDestination; + @Nullable private final HubServiceInfo mServiceInfo; + + private final AtomicBoolean mIsClosed = new AtomicBoolean(true); + + /** @hide */ + HubEndpointSession( + int id, + @NonNull HubEndpoint hubEndpoint, + @NonNull HubEndpointInfo destination, + @NonNull HubEndpointInfo initiator, + @Nullable HubServiceInfo serviceInfo) { + mId = id; + mHubEndpoint = hubEndpoint; + mDestination = destination; + mInitiator = initiator; + mServiceInfo = serviceInfo; + } + + /** + * Send a message to the peer endpoint in this session. + * + * @param message The message object constructed with {@link HubMessage#createMessage}. + * @return For messages that does not require a response, the transaction will immediately + * complete. For messages that requires a response, the transaction will complete after + * receiving the response for the message. + */ + @NonNull + public ContextHubTransaction sendMessage(@NonNull HubMessage message) { + if (mIsClosed.get()) { + throw new IllegalStateException("Session is already closed."); + } + + boolean isResponseRequired = message.getDeliveryParams().isResponseRequired(); + ContextHubTransaction ret = + new ContextHubTransaction<>( + isResponseRequired + ? ContextHubTransaction.TYPE_HUB_MESSAGE_REQUIRES_RESPONSE + : ContextHubTransaction.TYPE_HUB_MESSAGE_DEFAULT); + if (!isResponseRequired) { + // If the message doesn't require acknowledgement, respond with success immediately + // TODO(b/379162322): Improve handling of synchronous failures. + mHubEndpoint.sendMessage(this, message, null); + ret.setResponse( + new ContextHubTransaction.Response<>( + ContextHubTransaction.RESULT_SUCCESS, null)); + } else { + IContextHubTransactionCallback callback = + ContextHubTransactionHelper.createTransactionCallback(ret); + // Sequence number will be assigned at the service + mHubEndpoint.sendMessage(this, message, callback); + } + return ret; + } + + /** @hide */ + public int getId() { + return mId; + } + + /** @hide */ + public void setOpened() { + mIsClosed.set(false); + mCloseGuard.open("close"); + } + + /** @hide */ + public void setClosed() { + mIsClosed.set(true); + mCloseGuard.close(); + } + + /** + * Closes the connection for this session between an endpoint and the Context Hub Service. + * + *

When this function is invoked, the messaging associated with this session is invalidated. + * All futures messages targeted for this client are dropped. + */ + public void close() { + if (!mIsClosed.getAndSet(true)) { + mCloseGuard.close(); + mHubEndpoint.closeSession(this); + } + } + + /** + * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there + * is no service associated to this session. + * + *

For hub initiated sessions, the object was previously used in as an argument for open + * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. + * + *

For app initiated sessions, the object was previously used in an open request in {@link + * android.hardware.location.ContextHubManager#openSession} + */ + @Nullable + public HubServiceInfo getServiceInfo() { + return mServiceInfo; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Session ["); + stringBuilder.append(mId); + stringBuilder.append("]: ["); + stringBuilder.append(mInitiator); + stringBuilder.append("]->["); + stringBuilder.append(mDestination); + stringBuilder.append("]"); + return stringBuilder.toString(); + } + + /** @hide */ + protected void finalize() throws Throwable { + try { + // Note that guard could be null if the constructor threw. + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } + } +} diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java new file mode 100644 index 0000000000000000000000000000000000000000..1f2bdb9850087ead2dc37c497a696a255379d5ca --- /dev/null +++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; + +/** + * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines + * whether a open session request from the remote is accepted or not. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public class HubEndpointSessionResult { + private final boolean mAccepted; + + @Nullable private final String mReason; + + private HubEndpointSessionResult(boolean accepted, @Nullable String reason) { + mAccepted = accepted; + mReason = reason; + } + + /** + * Retrieve the decision of the session request. + * + * @return Whether a session request was accepted or not, previously set with {@link #accept()} + * or {@link #reject(String)}. + */ + public boolean isAccepted() { + return mAccepted; + } + + /** + * Retrieve the decision of the session request. + * + * @return The reason previously set in {@link #reject(String)}. If the result was {@link + * #accept()}, the reason will be null. + */ + @Nullable + public String getReason() { + return mReason; + } + + /** Accept the request. */ + @NonNull + public static HubEndpointSessionResult accept() { + return new HubEndpointSessionResult(true, null); + } + + /** + * Reject the request with a reason. + * + * @param reason Reason why the request was rejected, for diagnostic purposes. + */ + @NonNull + public static HubEndpointSessionResult reject(@NonNull String reason) { + return new HubEndpointSessionResult(false, reason); + } +} diff --git a/core/java/android/hardware/contexthub/HubMessage.aidl b/core/java/android/hardware/contexthub/HubMessage.aidl new file mode 100644 index 0000000000000000000000000000000000000000..86afce2330623981444846e6322ad0ec224819fa --- /dev/null +++ b/core/java/android/hardware/contexthub/HubMessage.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +/** + * @hide + */ +parcelable HubMessage; diff --git a/core/java/android/hardware/contexthub/HubMessage.java b/core/java/android/hardware/contexthub/HubMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..dc8a8c52ea5596cbae49353a3277273781d4d572 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubMessage.java @@ -0,0 +1,289 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import libcore.util.HexEncoding; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A class describing general messages send through the Context Hub Service. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubMessage implements Parcelable { + private static final int DEBUG_LOG_NUM_BYTES = 16; + + private final int mMessageType; + private final byte[] mMessageBody; + + private final DeliveryParams mDeliveryParams; + private int mMessageSequenceNumber; + + /** + * Configurable options for message delivery. This option can be passed into {@link + * HubEndpointSession#sendMessage} to specify the behavior of message delivery. + */ + public static class DeliveryParams { + private boolean mResponseRequired; + + private DeliveryParams(boolean responseRequired) { + mResponseRequired = responseRequired; + } + + /** Get the acknowledgement requirement. */ + public boolean isResponseRequired() { + return mResponseRequired; + } + + /** + * Set the response requirement for a message. Message sent with this option will have a + * {@link android.hardware.location.ContextHubTransaction.Response} when the peer received + * the message. Default is false. + */ + @NonNull + public DeliveryParams setResponseRequired(boolean required) { + mResponseRequired = required; + return this; + } + + /** Construct a default delivery option. */ + @NonNull + public static DeliveryParams makeBasic() { + return new DeliveryParams(false); + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("DeliveryParams["); + out.append("responseRequired = ").append(mResponseRequired); + out.append("]"); + return out.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mResponseRequired); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof DeliveryParams other) { + return other.mResponseRequired == mResponseRequired; + } + + return false; + } + } + + private HubMessage(int messageType, byte[] messageBody, DeliveryParams deliveryParams) { + mMessageType = messageType; + mMessageBody = messageBody; + mDeliveryParams = deliveryParams; + } + + /** + * Creates a HubMessage object to send to through an endpoint. + * + * @param messageType the endpoint & service dependent message type + * @param messageBody the byte array message contents + * @return the HubMessage object + */ + @NonNull + public static HubMessage createMessage(int messageType, @NonNull byte[] messageBody) { + return new HubMessage(messageType, messageBody, DeliveryParams.makeBasic()); + } + + /** + * Creates a HubMessage object to send to through an endpoint. + * + * @param messageType the endpoint & service dependent message type + * @param messageBody the byte array message contents + * @param deliveryParams The message delivery parameters. See {@link HubMessage.DeliveryParams} + * for more details. + * @return the HubMessage object + */ + @NonNull + public static HubMessage createMessage( + int messageType, @NonNull byte[] messageBody, @NonNull DeliveryParams deliveryParams) { + return new HubMessage(messageType, messageBody, deliveryParams); + } + + /** + * Retrieve the message type. + * + * @return the type of the message + */ + public int getMessageType() { + return mMessageType; + } + + /** + * Retrieve the body of the message. The body can be an empty byte array. + * + * @return the byte array contents of the message + */ + @NonNull + public byte[] getMessageBody() { + return mMessageBody; + } + + /** + * Retrieve the {@link DeliveryParams} object specifying the behavior of message delivery. + * + * @hide + */ + public DeliveryParams getDeliveryParams() { + return mDeliveryParams; + } + + /** + * Assign a message sequence number. This should only be called by the system service. + * + * @hide + */ + public void setMessageSequenceNumber(int messageSequenceNumber) { + mMessageSequenceNumber = messageSequenceNumber; + } + + /** + * Returns the message sequence number. The default value is 0. + * + * @return the message sequence number of the message + * @hide + */ + public int getMessageSequenceNumber() { + return mMessageSequenceNumber; + } + + private HubMessage(@NonNull Parcel in) { + mMessageType = in.readInt(); + + int msgSize = in.readInt(); + mMessageBody = new byte[msgSize]; + in.readByteArray(mMessageBody); + + mDeliveryParams = DeliveryParams.makeBasic(); + mDeliveryParams.setResponseRequired(in.readInt() == 1); + mMessageSequenceNumber = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mMessageType); + + out.writeInt(mMessageBody.length); + out.writeByteArray(mMessageBody); + + out.writeInt(mDeliveryParams.isResponseRequired() ? 1 : 0); + out.writeInt(mMessageSequenceNumber); + } + + public static final @NonNull Creator CREATOR = + new Creator<>() { + @Override + public HubMessage createFromParcel(Parcel in) { + return new HubMessage(in); + } + + @Override + public HubMessage[] newArray(int size) { + return new HubMessage[size]; + } + }; + + @NonNull + @Override + public String toString() { + int length = mMessageBody.length; + + StringBuilder out = new StringBuilder(); + out.append("HubMessage[type = ").append(mMessageType); + out.append(", length = ").append(mMessageBody.length); + out.append(", messageSequenceNumber = ").append(mMessageSequenceNumber); + out.append(", deliveryParams = ").append(mDeliveryParams); + out.append("]("); + + if (length > 0) { + out.append("data = 0x"); + } + for (int i = 0; i < Math.min(length, DEBUG_LOG_NUM_BYTES); i++) { + out.append(HexEncoding.encodeToString(mMessageBody[i], true /* upperCase */)); + + if ((i + 1) % 4 == 0) { + out.append(" "); + } + } + if (length > DEBUG_LOG_NUM_BYTES) { + out.append("..."); + } + out.append(")"); + + return out.toString(); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + + boolean isEqual = false; + if (object instanceof HubMessage other) { + isEqual = + (other.getMessageType() == mMessageType) + && Arrays.equals(other.getMessageBody(), mMessageBody) + && (other.getDeliveryParams().equals(mDeliveryParams)) + && (other.getMessageSequenceNumber() == mMessageSequenceNumber); + } + + return isEqual; + } + + @Override + public int hashCode() { + if (!Flags.fixApiCheck()) { + return super.hashCode(); + } + + return Objects.hash( + mMessageType, + Arrays.hashCode(mMessageBody), + mDeliveryParams, + mMessageSequenceNumber); + } +} diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.aidl b/core/java/android/hardware/contexthub/HubServiceInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..98b1bbab8b60dceb1aeabc5b7efcdc62dc52516b --- /dev/null +++ b/core/java/android/hardware/contexthub/HubServiceInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +/** + * @hide + */ +parcelable HubServiceInfo; diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..c7fe77c4a0f1589aeab1dda96e0d4bc02d71ed95 --- /dev/null +++ b/core/java/android/hardware/contexthub/HubServiceInfo.java @@ -0,0 +1,263 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableHolder; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.Objects; + +/** + * A class describing services provided by endpoints. + * + *

An endpoint can provide zero or more service. See {@link + * HubEndpoint.Builder#setServiceInfoCollection(Collection)} and {@link + * HubEndpointInfo#getServiceInfoCollection()}. + * + *

An endpoint session can be service-less or associated to one service.See {@link + * HubEndpointSession#getServiceInfo()}. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubServiceInfo implements Parcelable { + /** Customized format for messaging. Fully customized and opaque messaging format. */ + public static final int FORMAT_CUSTOM = 0; + + /** + * Binder-based messaging. The host endpoint is defining this service in Stable AIDL. Messages + * between endpoints that uses this service will be using the binder marhsalling format. + */ + public static final int FORMAT_AIDL = 1; + + /** + * Pigweed RPC messaging with Protobuf. This endpoint is a Pigweed RPC. Messages between + * endpoints will use Pigweed RPC marshalling format (protobuf). + */ + public static final int FORMAT_PW_RPC_PROTOBUF = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FORMAT_CUSTOM, + FORMAT_AIDL, + FORMAT_PW_RPC_PROTOBUF, + }) + public @interface ServiceFormat {} + + @NonNull private final String mServiceDescriptor; + + @ServiceFormat private final int mFormat; + private final int mMajorVersion; + private final int mMinorVersion; + + @NonNull private final ParcelableHolder mExtendedInfo; + + /** @hide */ + public HubServiceInfo(android.hardware.contexthub.Service service) { + mServiceDescriptor = service.serviceDescriptor; + mFormat = service.format; + mMajorVersion = service.majorVersion; + mMinorVersion = service.minorVersion; + mExtendedInfo = service.extendedInfo; + } + + private HubServiceInfo(Parcel in) { + mServiceDescriptor = Objects.requireNonNull(in.readString()); + mFormat = in.readInt(); + mMajorVersion = in.readInt(); + mMinorVersion = in.readInt(); + mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in); + } + + public HubServiceInfo( + @NonNull String serviceDescriptor, + @ServiceFormat int format, + int majorVersion, + int minorVersion, + @NonNull ParcelableHolder extendedInfo) { + mServiceDescriptor = serviceDescriptor; + mFormat = format; + mMajorVersion = majorVersion; + mMinorVersion = minorVersion; + mExtendedInfo = extendedInfo; + } + + /** Get the unique identifier of this service. See {@link Builder} for more information. */ + @NonNull + public String getServiceDescriptor() { + return mServiceDescriptor; + } + + /** + * Get the type of the service. + * + *

The value can be one of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link + * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. + */ + public int getFormat() { + return mFormat; + } + + /** Get the major version of this service. */ + public int getMajorVersion() { + return mMajorVersion; + } + + /** Get the minor version of this service. */ + public int getMinorVersion() { + return mMinorVersion; + } + + /** Get the {@link ParcelableHolder} for the extended information about the service. */ + @NonNull + public ParcelableHolder getExtendedInfo() { + return mExtendedInfo; + } + + /** Parcel implementation details */ + @Override + public int describeContents() { + // Passthrough describeContents flags for mExtendedInfo because we don't have FD otherwise. + return mExtendedInfo.describeContents(); + } + + /** Parcel implementation details */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mServiceDescriptor); + dest.writeInt(mFormat); + dest.writeInt(mMajorVersion); + dest.writeInt(mMinorVersion); + mExtendedInfo.writeToParcel(dest, flags); + } + + /** Builder for a {@link HubServiceInfo} object. */ + public static final class Builder { + @NonNull private final String mServiceDescriptor; + + @ServiceFormat private final int mFormat; + private final int mMajorVersion; + private final int mMinorVersion; + + private final ParcelableHolder mExtendedInfo = + new ParcelableHolder(Parcelable.PARCELABLE_STABILITY_VINTF); + + /** + * Create a builder for {@link HubServiceInfo} with a service descriptor. + * + *

Service descriptor should uniquely identify the interface (scoped to type). Convention + * of the descriptor depend on interface type. + * + *

Examples: + * + *

    + *
  1. AOSP-defined AIDL: android.hardware.something.IFoo/default + *
  2. Vendor-defined AIDL: com.example.something.IBar/default + *
  3. Pigweed RPC with Protobuf: com.example.proto.ExampleService + *
+ * + * @param serviceDescriptor The service descriptor. + * @param format One of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link + * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}. + * @param majorVersion Breaking changes should be a major version bump. + * @param minorVersion Monotonically increasing minor version. + * @throws IllegalArgumentException if one or more fields are not valid. + */ + public Builder( + @NonNull String serviceDescriptor, + @ServiceFormat int format, + int majorVersion, + int minorVersion) { + if (format != FORMAT_CUSTOM + && format != FORMAT_AIDL + && format != FORMAT_PW_RPC_PROTOBUF) { + throw new IllegalArgumentException("Invalid format type."); + } + mFormat = format; + + if (majorVersion < 0) { + throw new IllegalArgumentException( + "Major version cannot be set to negative number."); + } + mMajorVersion = majorVersion; + + if (minorVersion < 0) { + throw new IllegalArgumentException( + "Minor version cannot be set to negative number."); + } + mMinorVersion = minorVersion; + + if (serviceDescriptor.isBlank()) { + throw new IllegalArgumentException("Invalid service descriptor."); + } + mServiceDescriptor = serviceDescriptor; + } + + /** + * Set the extended information of this service. + * + * @param extendedInfo Parcelable with extended information about this service. The + * parcelable needs to have at least VINTF stability. Null can be used to clear a + * previously set value. + * @throws android.os.BadParcelableException if the parcelable cannot be used. + */ + @NonNull + public Builder setExtendedInfo(@Nullable Parcelable extendedInfo) { + mExtendedInfo.setParcelable(extendedInfo); + return this; + } + + /** + * Build the {@link HubServiceInfo} object. + * + * @throws IllegalStateException if the Builder is missing required info. + */ + @NonNull + public HubServiceInfo build() { + if (mMajorVersion < 0 || mMinorVersion < 0) { + throw new IllegalStateException("Major and minor version must be set."); + } + return new HubServiceInfo( + mServiceDescriptor, mFormat, mMajorVersion, mMinorVersion, mExtendedInfo); + } + } + + /** Parcel implementation details */ + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator<>() { + public HubServiceInfo createFromParcel(Parcel in) { + return new HubServiceInfo(in); + } + + public HubServiceInfo[] newArray(int size) { + return new HubServiceInfo[size]; + } + }; +} diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1c98b4b3f4f569d6e56d25de180d9001a12b5eec --- /dev/null +++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl @@ -0,0 +1,90 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubMessage; +import android.hardware.contexthub.HubServiceInfo; +import android.hardware.location.IContextHubTransactionCallback; + +/** + * @hide + */ +interface IContextHubEndpoint { + /** + * Retrieve the up-to-date EndpointInfo, with assigned endpoint id. + */ + HubEndpointInfo getAssignedHubEndpointInfo(); + + /** + * Request system service to open a session with a specific destination. + * + * @param destination A valid HubEndpointInfo representing the destination. + * + * @throws IllegalArgumentException If the HubEndpointInfo is not valid. + * @throws IllegalStateException If there are too many opened sessions. + */ + int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo); + + /** + * Request system service to close a specific session + * + * @param sessionId An integer identifying the session, assigned by system service + * @param reason An integer identifying the reason + * + * @throws IllegalStateException If the session wasn't opened. + */ + void closeSession(int sessionId, int reason); + + /** + * Callback when a session is opened. This callback is the status callback for a previous + * IContextHubEndpointCallback.onSessionOpenRequest(). + * + * @param sessionId The integer representing the communication session, previously set in + * onSessionOpenRequest(). + * + * @throws IllegalStateException If the session wasn't opened. + */ + void openSessionRequestComplete(int sessionId); + + /** + * Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned. + */ + void unregister(); + + /** + * Send a message parcelable to system service for a specific session. + * + * @param sessionId The integer representing the communication session, previously set in + * IContextHubEndpoint.openSession(). This id is assigned by the HAL. + * @param message The HubMessage parcelable that represents the message and its delivery options. + * @param transactionCallback Nullable. If the hub message requires a reply, the transactionCallback + * will be set to non-null. + */ + void sendMessage(int sessionId, in HubMessage message, + in @nullable IContextHubTransactionCallback transactionCallback); + + /** + * Send a message delivery status to system service for a specific message + * + * @param sessionId The integer representing the communication session, previously set in + * IContextHubEndpoint.openSession(). This id is assigned by the HAL. + * @param messageSeqNumber The message sequence number, this should match a previously received HubMessage. + * @param errorCode The message delivery status detail. + */ + void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode); +} diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1ae5fb9d28c1a2d989ed1f41a629dff648f67c49 --- /dev/null +++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl @@ -0,0 +1,61 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubMessage; +import android.hardware.contexthub.HubServiceInfo; + +/** + * @hide + */ +oneway interface IContextHubEndpointCallback { + /** + * Request from system service to open a session, requested by a specific initiator. + * + * @param sessionId An integer identifying the session, assigned by the initiator + * @param initiator HubEndpointInfo representing the requester + * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session + */ + void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo); + + /** + * Request from system service to close a specific session + * + * @param sessionId An integer identifying the session + * @param reason An integer identifying the reason + */ + void onSessionClosed(int sessionId, int reason); + + /** + * Notifies the system service that the session requested by IContextHubEndpoint.openSession + * is ready to use. + * + * @param sessionId The integer representing the communication session, previously set in + * IContextHubEndpoint.openSession(). This id is assigned by the HAL. + */ + void onSessionOpenComplete(int sessionId); + + /** + * Message notification from system service for a specific session + + * @param sessionId The integer representing the communication session, previously set in + * IContextHubEndpoint.openSession(). This id is assigned by the HAL. + * @param message The HubMessage parcelable that represents the message. + */ + void onMessageReceived(int sessionId, in HubMessage message); +} diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..46884393b49ba9ec89bc5ddd5b0b606943be301a --- /dev/null +++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.chre.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Interface for listening to lifecycle events of a hub endpoint. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public interface IHubEndpointLifecycleCallback { + /** Unknown reason. */ + int REASON_UNSPECIFIED = 0; + + /** The peer rejected the request to open this endpoint session. */ + int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; + + /** The peer closed this endpoint session. */ + int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + REASON_UNSPECIFIED, + REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED, + REASON_CLOSE_ENDPOINT_SESSION_REQUESTED, + }) + @interface EndpointLifecycleReason {} + + /** + * Called when an endpoint is requesting a session be opened with another endpoint. + * + * @param requester The {@link HubEndpointInfo} object representing the requester + * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with + * this session. Null indicates that there is no service associated with this session. + */ + @NonNull + HubEndpointSessionResult onSessionOpenRequest( + @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo); + + /** + * Called when a communication session is opened and ready to be used. + * + * @param session The {@link HubEndpointSession} object that can be used for communication + */ + void onSessionOpened(@NonNull HubEndpointSession session); + + /** + * Called when a communication session is requested to be closed, or the peer endpoint rejected + * the session open request. + * + * @param session The {@link HubEndpointSession} object that is now closed and shouldn't be + * used. + * @param reason The reason why this session was closed. + */ + void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason); +} diff --git a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java b/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..fde7017b5e76d1775b0b2987f2e2c1239b1630da --- /dev/null +++ b/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.contexthub; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.chre.flags.Flags; + +/** + * An interface used to deliver messages to an opened endpoint session. + * + *

This interface can be attached to an endpoint through {@link + * HubEndpoint.Builder#setMessageCallback} method. Methods in this interface will only be called + * when the endpoint is currently registered and has an open session. The endpoint will receive + * session lifecycle callbacks through {@link IHubEndpointLifecycleCallback}. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public interface IHubEndpointMessageCallback { + /** + * Callback interface for receiving messages for a particular endpoint session. + * + * @param session The session this message is sent through. Previously specified in a {@link + * IHubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call. + * @param message The {@link HubMessage} object representing a message received by the endpoint + * that registered this callback interface. This message is constructed by the + */ + void onMessageReceived(@NonNull HubEndpointSession session, @NonNull HubMessage message); +} diff --git a/core/java/android/hardware/contexthub/OWNERS b/core/java/android/hardware/contexthub/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..a65a2bf9ee3615116b6ada6a8966eb1921b8d716 --- /dev/null +++ b/core/java/android/hardware/contexthub/OWNERS @@ -0,0 +1,2 @@ +# ContextHub team +file:platform/system/chre:/OWNERS diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index e583627c09600a0e4fced944a8789ece57209fde..8b4d0da147bccb85c9e0940bb69ce581e7d3c856 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -172,6 +172,23 @@ public final class DeviceState { */ public static final int PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT = 17; + /** + * Property that indicates that this state corresponds to the device state for rear display + * mode, where both the inner and outer displays are on. In this state, the outer display + * is the default display where the app is shown, and the inner display is used by the system to + * show a UI affordance for exiting the mode. + * + * Note that this value should generally not be used, and may be removed in the future (e.g. + * if or when it becomes the only type of rear display mode when + * {@link android.hardware.devicestate.feature.flags.Flags#deviceStateRdmV2} is removed). + * + * As such, clients should strongly consider relying on {@link #PROPERTY_FEATURE_REAR_DISPLAY} + * instead. + * + * @hide + */ + public static final int PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT = 1001; + /** @hide */ @IntDef(prefix = {"PROPERTY_"}, flag = false, value = { PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED, @@ -190,7 +207,8 @@ public final class DeviceState { PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE, PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY, PROPERTY_FEATURE_REAR_DISPLAY, - PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT + PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT, + PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT }) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig index 98ba9192044d03d7a63e18fc82501e2d92d475ea..6230f4dbf6f46bd319ffc6e7864864b339536500 100644 --- a/core/java/android/hardware/devicestate/feature/flags.aconfig +++ b/core/java/android/hardware/devicestate/feature/flags.aconfig @@ -29,4 +29,13 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "device_state_rdm_v2" + is_exported: true + namespace: "windowing_sdk" + description: "Enables Rear Display Mode V2, where the inner display shows the user a UI affordance for exiting the state" + bug: "372486634" + is_fixed_read_only: true } \ No newline at end of file diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 28da644dd837710f5796427a4e39427e34fc5fdf..25327a9b1d52583d7b7a36350c97bd089ea55981 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -21,6 +21,8 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.HdrCapabilities.HdrType; import static android.view.Display.INVALID_DISPLAY; +import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS; + import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.FloatRange; @@ -59,6 +61,7 @@ import android.view.Surface; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.server.display.feature.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -100,6 +103,7 @@ public final class DisplayManager { private final WeakDisplayCache mDisplayCache = new WeakDisplayCache(); private int mDisplayIdToMirror = INVALID_DISPLAY; + private AmbientDisplayConfiguration mAmbientDisplayConfiguration; /** * Broadcast receiver that indicates when the Wifi display status changes. @@ -576,6 +580,8 @@ public final class DisplayManager { EVENT_FLAG_DISPLAY_ADDED, EVENT_FLAG_DISPLAY_CHANGED, EVENT_FLAG_DISPLAY_REMOVED, + EVENT_FLAG_DISPLAY_REFRESH_RATE, + EVENT_FLAG_DISPLAY_STATE }) @Retention(RetentionPolicy.SOURCE) public @interface EventFlag {} @@ -596,8 +602,8 @@ public final class DisplayManager { * * @see #registerDisplayListener(DisplayListener, Handler, long) * - * @hide */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) public static final long EVENT_FLAG_DISPLAY_ADDED = 1L << 0; /** @@ -605,8 +611,8 @@ public final class DisplayManager { * * @see #registerDisplayListener(DisplayListener, Handler, long) * - * @hide */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) public static final long EVENT_FLAG_DISPLAY_REMOVED = 1L << 1; /** @@ -614,10 +620,27 @@ public final class DisplayManager { * * @see #registerDisplayListener(DisplayListener, Handler, long) * - * @hide */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; + + /** + * Event flag to register for a display's refresh rate changes. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) + public static final long EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 3; + + /** + * Event flag to register for a display state changes. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) + public static final long EVENT_FLAG_DISPLAY_STATE = 1L << 4; + /** * Event flag to register for a display's brightness changes. This notification is sent * through the {@link DisplayListener#onDisplayChanged} callback method. New brightness @@ -787,9 +810,6 @@ public final class DisplayManager { * if the listener should be invoked on the calling thread's looper. * @param eventFlags A bitmask of the event types for which this listener is subscribed. * - * @see #EVENT_FLAG_DISPLAY_ADDED - * @see #EVENT_FLAG_DISPLAY_CHANGED - * @see #EVENT_FLAG_DISPLAY_REMOVED * @see #registerDisplayListener(DisplayListener, Handler) * @see #unregisterDisplayListener * @@ -802,6 +822,25 @@ public final class DisplayManager { ActivityThread.currentPackageName()); } + /** + * Registers a display listener to receive notifications about given display event types. + * + * @param listener The listener to register. + * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. + * @param eventFlags A bitmask of the event types for which this listener is subscribed. + * + * @see #registerDisplayListener(DisplayListener, Handler) + * @see #unregisterDisplayListener + * + */ + @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS) + public void registerDisplayListener(@NonNull Executor executor, @EventFlag long eventFlags, + @NonNull DisplayListener listener) { + mGlobal.registerDisplayListener(listener, executor, + mGlobal.mapFlagsToInternalEventFlag(eventFlags, 0), + ActivityThread.currentPackageName()); + } + /** * Registers a display listener to receive notifications about given display event types. * @@ -812,12 +851,6 @@ public final class DisplayManager { * @param privateEventFlags A bitmask of the private event types for which this listener * is subscribed. * - * @see #EVENT_FLAG_DISPLAY_ADDED - * @see #EVENT_FLAG_DISPLAY_CHANGED - * @see #EVENT_FLAG_DISPLAY_REMOVED - * @see #PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS - * @see #PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED - * @see #PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED * @see #registerDisplayListener(DisplayListener, Handler) * @see #unregisterDisplayListener * @@ -1581,6 +1614,17 @@ public final class DisplayManager { return mGlobal.shouldAlwaysRespectAppRequestedMode(); } + /** + * Returns whether this device supports Always On Display. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_IS_ALWAYS_ON_AVAILABLE_API) + public boolean isAlwaysOnDisplayCurrentlyAvailable() { + return getAmbientDisplayConfiguration().alwaysOnAvailableForUser(mContext.getUserId()); + } + /** * Returns whether device supports seamless refresh rate switching. * @@ -1643,6 +1687,15 @@ public final class DisplayManager { } } + private AmbientDisplayConfiguration getAmbientDisplayConfiguration() { + synchronized (this) { + if (mAmbientDisplayConfiguration == null) { + mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + } + } + return mAmbientDisplayConfiguration; + } + /** * Creates a VirtualDisplay that will mirror the content of displayIdToMirror * @param name The name for the virtual display diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 03b44f63e3b76a038083bb38d409667dcb1f570d..1e66beea42a63ff95498f0e1d2af95242ab1ecd1 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -17,6 +17,7 @@ package android.hardware.display; +import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; import static android.hardware.display.DisplayManager.EventFlag; import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.view.Display.HdrCapabilities.HdrType; @@ -62,6 +63,7 @@ import android.view.DisplayInfo; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -108,6 +110,8 @@ public final class DisplayManagerGlobal { EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED, EVENT_DISPLAY_CONNECTED, EVENT_DISPLAY_DISCONNECTED, + EVENT_DISPLAY_REFRESH_RATE_CHANGED, + EVENT_DISPLAY_STATE_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface DisplayEvent {} @@ -119,6 +123,8 @@ public final class DisplayManagerGlobal { public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5; public static final int EVENT_DISPLAY_CONNECTED = 6; public static final int EVENT_DISPLAY_DISCONNECTED = 7; + public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8; + public static final int EVENT_DISPLAY_STATE_CHANGED = 9; @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = { INTERNAL_EVENT_FLAG_DISPLAY_ADDED, @@ -127,6 +133,8 @@ public final class DisplayManagerGlobal { INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, + INTERNAL_EVENT_FLAG_DISPLAY_STATE }) @Retention(RetentionPolicy.SOURCE) public @interface InternalEventFlag {} @@ -137,6 +145,8 @@ public final class DisplayManagerGlobal { public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3; public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4; public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7; @UnsupportedAppUsage private static DisplayManagerGlobal sInstance; @@ -179,9 +189,11 @@ public final class DisplayManagerGlobal { } private PropertyInvalidatedCache mDisplayCache = - new PropertyInvalidatedCache( - 8, // size of display cache - CACHE_KEY_DISPLAY_INFO_PROPERTY) { + new PropertyInvalidatedCache<>( + new PropertyInvalidatedCache.Args(MODULE_SYSTEM) + .maxEntries(8).api(CACHE_KEY_DISPLAY_INFO_API).isolateUids(false), + CACHE_KEY_DISPLAY_INFO_API, null) { + @Override public DisplayInfo recompute(Integer id) { try { @@ -1427,6 +1439,18 @@ public final class DisplayManagerGlobal { mListener.onDisplayDisconnected(displayId); } break; + case EVENT_DISPLAY_REFRESH_RATE_CHANGED: + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0) { + mListener.onDisplayChanged(displayId); + } + break; + case EVENT_DISPLAY_STATE_CHANGED: + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0) { + mListener.onDisplayChanged(displayId); + } + break; } if (DEBUG) { Trace.endSection(); @@ -1493,18 +1517,17 @@ public final class DisplayManagerGlobal { } /** - * Name of the property containing a unique token which changes every time we update the - * system's display configuration. + * The API portion of the key that identifies the unique PropertyInvalidatedCache token which + * changes every time we update the system's display configuration. */ - public static final String CACHE_KEY_DISPLAY_INFO_PROPERTY = - PropertyInvalidatedCache.createSystemCacheKey("display_info"); + private static final String CACHE_KEY_DISPLAY_INFO_API = "display_info"; /** * Invalidates the contents of the display info cache for all applications. Can only * be called by system_server. */ public static void invalidateLocalDisplayInfoCaches() { - PropertyInvalidatedCache.invalidateCache(CACHE_KEY_DISPLAY_INFO_PROPERTY); + PropertyInvalidatedCache.invalidateCache(MODULE_SYSTEM, CACHE_KEY_DISPLAY_INFO_API); } /** @@ -1566,6 +1589,10 @@ public final class DisplayManagerGlobal { return "EVENT_DISPLAY_CONNECTED"; case EVENT_DISPLAY_DISCONNECTED: return "EVENT_DISPLAY_DISCONNECTED"; + case EVENT_DISPLAY_REFRESH_RATE_CHANGED: + return "EVENT_DISPLAY_REFRESH_RATE_CHANGED"; + case EVENT_DISPLAY_STATE_CHANGED: + return "EVENT_DISPLAY_STATE_CHANGED"; } return "UNKNOWN"; } @@ -1630,6 +1657,17 @@ public final class DisplayManagerGlobal { baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; } + if (Flags.displayListenerPerformanceImprovements()) { + if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; + } + + if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_STATE) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE; + } + } + + return baseEventMask; } } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 399184cfaecb5cf54d96e89f6ca96409bdf70fb3..68b6cfc012fc50a7dd7e5731cabec4addc57931a 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -470,6 +470,31 @@ public abstract class DisplayManagerInternal { */ public abstract boolean isDisplayReadyForMirroring(int displayId); + + /** + * Used by the window manager to override the per-display screen brightness based on the + * current foreground activity. + * + * The key of the array is the displayId. If a displayId is missing from the array, this is + * equivalent to clearing any existing brightness overrides for that display. + * + * This method must only be called by the window manager. + */ + public abstract void setScreenBrightnessOverrideFromWindowManager( + SparseArray brightnessOverrides); + + /** + * Describes a request for overriding the brightness of a single display. + */ + public static class DisplayBrightnessOverrideRequest { + // An override of the screen brightness. + // Set to PowerManager.BRIGHTNESS_INVALID if there's no override. + public float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + + // Tag used to identify the app window requesting the brightness override. + public CharSequence tag; + } + /** * Describes the requested power state of the display. * @@ -505,11 +530,11 @@ public abstract class DisplayManagerInternal { // nearby, turning it off temporarily until the object is moved away. public boolean useProximitySensor; - // An override of the screen brightness. + // A global override of the screen brightness, applied to all displays. // Set to PowerManager.BRIGHTNESS_INVALID if there's no override. public float screenBrightnessOverride; - // Tag used to identify the app window requesting the brightness override. + // Tag used to identify the reason for the global brightness override. public CharSequence screenBrightnessOverrideTag; // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index e349b81614bc9fa2a5086d77aed8a94f60dd313e..f00c3a53ad0cb050a685001ea2714b4aa5c8672f 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -23,6 +23,7 @@ import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP; import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.PointF; import android.graphics.RectF; import android.os.Parcel; import android.os.Parcelable; @@ -150,6 +151,138 @@ public final class DisplayTopology implements Parcelable { } } + /** + * Rearranges the topology toward the positions given for each display. The width and height of + * each display, as well as the primary display, are not changed by this call. + *

+ * Upon returning, the topology will be valid and normalized with each display as close to the + * requested positions as possible. + * + * @param newPos the desired positions (upper-left corner) of each display. The keys in the map + * are the display IDs. + * @throws IllegalArgumentException if the keys in {@code positions} are not the exact display + * IDs in this topology, no more, no less + */ + public void rearrange(Map newPos) { + var availableParents = new ArrayList(); + + availableParents.addLast(mRoot); + + var needsParent = allNodesIdMap(); + + // In the case of missing items, if this check doesn't detect it, a NPE will be thrown + // later. + if (needsParent.size() != newPos.size()) { + throw new IllegalArgumentException("newPos has wrong number of entries: " + newPos); + } + + mRoot.mChildren.clear(); + for (TreeNode n : needsParent.values()) { + n.mChildren.clear(); + } + + needsParent.remove(mRoot.mDisplayId); + // Start with a root island and add children to it one-by-one until the island consists of + // all the displays. The root island begins with only the root node, which has no + // parent. Then we greedily choose an optimal pairing of two nodes, consisting of a node + // from the island and a node not yet in the island. This is repeating until all nodes are + // in the island. + // + // The optimal pair is the pair which has the smallest deviation. The deviation consists of + // an x-axis component and a y-axis component, called xDeviation and yDeviation. + // + // The deviations are like distances but a little different. They are calculated in two + // steps. The first step calculates both axes in a similar way. The next step compares the + // two values and chooses which axis to attach along. Depending on which axis is chosen, + // the deviation for one axis is updated. See below for details. + while (!needsParent.isEmpty()) { + double bestDist = Double.POSITIVE_INFINITY; + TreeNode bestChild = null, bestParent = null; + + for (var child : needsParent.values()) { + PointF childPos = newPos.get(child.mDisplayId); + float childRight = childPos.x + child.getWidth(); + float childBottom = childPos.y + child.getHeight(); + for (var parent : availableParents) { + PointF parentPos = newPos.get(parent.mDisplayId); + float parentRight = parentPos.x + parent.getWidth(); + float parentBottom = parentPos.y + parent.getHeight(); + + // This is the smaller of the two ranges minus the amount of overlap shared + // between them. The "amount of overlap" is negative if there is no overlap, but + // this does not make a parenting ineligible, because we allow for attaching at + // the corner and for floating point error. The overlap is more negative the + // farther apart the closest corner pair is. + // + // For each axis, this calculates (SmallerRange - Overlap). If one range lies + // completely in the other (or they are equal), the axis' deviation will be + // zero. + // + // The "SmallerRange," which refers to smaller of the widths of the two rects, + // or smaller of the heights of the two rects, is added to the deviation so that + // a maximum overlap results in a deviation of zero. + float xSmallerRange = Math.min(child.getWidth(), parent.getWidth()); + float ySmallerRange = Math.min(child.getHeight(), parent.getHeight()); + float xOverlap + = Math.min(parentRight, childRight) + - Math.max(parentPos.x, childPos.x); + float yOverlap + = Math.min(parentBottom, childBottom) + - Math.max(parentPos.y, childPos.y); + float xDeviation = xSmallerRange - xOverlap; + float yDeviation = ySmallerRange - yOverlap; + + float offset; + int pos; + if (xDeviation <= yDeviation) { + if (childPos.y < parentPos.y) { + yDeviation = childBottom - parentPos.y; + pos = POSITION_TOP; + } else { + yDeviation = parentBottom - childPos.y; + pos = POSITION_BOTTOM; + } + offset = childPos.x - parentPos.x; + } else { + if (childPos.x < parentPos.x) { + xDeviation = childRight - parentPos.x; + pos = POSITION_LEFT; + } else { + xDeviation = parentRight - childPos.x; + pos = POSITION_RIGHT; + } + offset = childPos.y - parentPos.y; + } + + double dist = Math.hypot(xDeviation, yDeviation); + if (dist >= bestDist) { + continue; + } + + bestDist = dist; + bestChild = child; + bestParent = parent; + // Eagerly update the child's parenting info, even though we may not use it, in + // which case it will be overwritten later. + bestChild.mPosition = pos; + bestChild.mOffset = offset; + } + } + + assert bestParent != null & bestChild != null; + + bestParent.addChild(bestChild); + if (null == needsParent.remove(bestChild.mDisplayId)) { + throw new IllegalStateException("child not in pending set! " + bestChild); + } + availableParents.add(bestChild); + } + + // The conversion may have introduced an intersection of two display rects. If they are + // bigger than our error tolerance, this function will remove them. + normalize(); + } + @Override public int describeContents() { return 0; @@ -450,6 +583,20 @@ public final class DisplayTopology implements Parcelable { return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON; } + private Map allNodesIdMap() { + var pend = new ArrayDeque(); + var found = new HashMap(); + + pend.push(mRoot); + do { + TreeNode node = pend.pop(); + found.put(node.mDisplayId, node); + pend.addAll(node.mChildren); + } while (!pend.isEmpty()); + + return found; + } + public static final class TreeNode implements Parcelable { public static final int POSITION_LEFT = 0; public static final int POSITION_TOP = 1; diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/flags.aconfig similarity index 63% rename from core/java/android/hardware/flags/overlayproperties_flags.aconfig rename to core/java/android/hardware/flags/flags.aconfig index 6c86108c4034b7824dbd6e418501d62a05fb09f6..5ca6c6bed1f0f190d8baf4a5b219bd0ac1f3386c 100644 --- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig +++ b/core/java/android/hardware/flags/flags.aconfig @@ -1,6 +1,15 @@ package: "android.hardware.flags" container: "system" +flag { + name: "luts_api" + is_exported: true + is_fixed_read_only: true + namespace: "core_graphics" + description: "public Luts related Apis" + bug: "349667978" +} + flag { name: "overlayproperties_class_api" is_exported: true diff --git a/core/java/android/hardware/input/AidlInputGestureData.aidl b/core/java/android/hardware/input/AidlInputGestureData.aidl index e33ec53dd20850b01c9bbe86156bf3db7d555d42..f7410d2e7783afa3e9ca8ba8e6ccc18e73396ed6 100644 --- a/core/java/android/hardware/input/AidlInputGestureData.aidl +++ b/core/java/android/hardware/input/AidlInputGestureData.aidl @@ -28,15 +28,18 @@ parcelable AidlInputGestureData { String appLaunchPackageName; String appLaunchClassName; + @JavaDerive(equals=true) parcelable KeyTrigger { int keycode; int modifierState; } + @JavaDerive(equals=true) parcelable TouchpadGestureTrigger { int gestureType; } + @JavaDerive(equals=true) union Trigger { KeyTrigger key; TouchpadGestureTrigger touchpadGesture; diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 96f6ad117035a3f2b9accd93fde6673ce372923a..71b60cff936762d77706f8b0448230bcf4b55475 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -31,6 +31,7 @@ import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut; import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; +import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures; import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; import static com.android.input.flags.Flags.enableInputFilterRustImpl; import static com.android.input.flags.Flags.keyboardRepeatKeys; @@ -1147,4 +1148,13 @@ public class InputSettings { public static boolean isCustomizableInputGesturesFeatureFlagEnabled() { return enableCustomizableInputGestures() && useKeyGestureEventHandler(); } + + /** + * Whether multi-key gestures are supported using {@code KeyGestureEventHandler} + * + * @hide + */ + public static boolean doesKeyGestureEventHandlerSupportMultiKeyGestures() { + return useKeyGestureEventHandler() && useKeyGestureEventHandlerMultiPressGestures(); + } } diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 24951c4d516e0339f34951445b155a65824818b7..711dc3a2cf7c49ce86c0b96b9cbcf1900d249c1c 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -115,12 +115,14 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS = 67; public static final int KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW = 68; public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69; - public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70; - public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71; + public static final int KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW = 70; + public static final int KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW = 71; public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72; public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73; public static final int KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74; public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75; + public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76; + public static final int FLAG_CANCELLED = 1; @@ -205,12 +207,13 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, - KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, - KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE, + KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW, + KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT, KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK, + KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { @@ -557,14 +560,6 @@ public final class KeyGestureEvent { return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE; case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION; - case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW: - return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_LEFT_FREEFORM_WINDOW; - case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW: - return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_RIGHT_FREEFORM_WINDOW; - case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW: - return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MAXIMIZE_FREEFORM_WINDOW; - case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE: - return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RESTORE_FREEFORM_WINDOW_SIZE; default: return LOG_EVENT_UNSPECIFIED; } @@ -777,10 +772,10 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW: return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW"; - case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW: - return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW"; - case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE: - return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE"; + case KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW: + return "KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW"; + case KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW: + return "KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN: return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN"; case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT: @@ -789,6 +784,8 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION"; case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK: return "KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK"; + case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW: + return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW"; default: return Integer.toHexString(value); } diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index a8eb11d88aa48ec2254fc0edeaf1e596a31e5266..fee074901c10f8f1593c58d1fb74f59fa5cb1c56 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -153,8 +153,8 @@ flag { flag { name: "override_power_key_behavior_in_focused_window" - namespace: "input_native" - description: "Allows privileged focused windows to capture power key events." + namespace: "wallet_integration" + description: "Allows privileged focused windows to override the power key double tap behavior." bug: "357144512" } @@ -170,4 +170,11 @@ flag { namespace: "input" description: "Adds key gestures for talkback and magnifier" bug: "375277034" -} \ No newline at end of file +} + +flag { + name: "can_window_override_power_gesture_api" + namespace: "wallet_integration" + description: "Adds new API in WindowManager class to check if the window can override the power key double tap behavior." + bug: "378736024" + } \ No newline at end of file diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java index 163f9fa83fe22dbf6d8561a1cb117542939c0381..38f34e961ec0443c32cd4fdf481cc1dd1a4b5728 100644 --- a/core/java/android/hardware/lights/Light.java +++ b/core/java/android/hardware/lights/Light.java @@ -70,6 +70,12 @@ public final class Light implements Parcelable { */ public static final int LIGHT_TYPE_KEYBOARD_MIC_MUTE = 10004; + /** + * Type for keyboard volume mute light. + * @hide + */ + public static final int LIGHT_TYPE_KEYBOARD_VOLUME_MUTE = 10005; + /** * Capability for lights that could adjust its LED brightness. If the capability is not present * the LED can only be turned either on or off. @@ -99,6 +105,7 @@ public final class Light implements Parcelable { LIGHT_TYPE_PLAYER_ID, LIGHT_TYPE_KEYBOARD_BACKLIGHT, LIGHT_TYPE_KEYBOARD_MIC_MUTE, + LIGHT_TYPE_KEYBOARD_VOLUME_MUTE, }) public @interface LightType {} diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 494bfc92638426c57d9c2b4f72ece47d1c4d3c25..426cd69f76a0a245c11c640952170f5fcb8ef921 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -34,6 +34,11 @@ import android.chre.flags.Flags; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; +import android.hardware.contexthub.HubDiscoveryInfo; +import android.hardware.contexthub.HubEndpoint; +import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubServiceInfo; +import android.hardware.contexthub.IHubEndpointLifecycleCallback; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -42,6 +47,7 @@ import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -678,6 +684,65 @@ public final class ContextHubManager { return transaction; } + /** + * Find a list of endpoints that matches a specific ID. + * + * @param endpointId Statically generated ID for an endpoint. + * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. + */ + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull + public List findEndpoints(long endpointId) { + // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load + // timing. + try { + List endpointInfos = mService.findEndpoints(endpointId); + List results = new ArrayList<>(endpointInfos.size()); + // Wrap with result type + for (HubEndpointInfo endpointInfo : endpointInfos) { + results.add(new HubDiscoveryInfo(endpointInfo)); + } + return results; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Find a list of endpoints that provides a specific service. + * + * @param serviceDescriptor Statically generated ID for an endpoint. + * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. + * @throws IllegalArgumentException if the serviceDescriptor is empty/null. + */ + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull + public List findEndpoints(@NonNull String serviceDescriptor) { + // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load + // timing. + if (serviceDescriptor.isBlank()) { + throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor); + } + try { + List endpointInfos = + mService.findEndpointsWithService(serviceDescriptor); + List results = new ArrayList<>(endpointInfos.size()); + // Wrap with result type + for (HubEndpointInfo endpointInfo : endpointInfos) { + for (HubServiceInfo serviceInfo : endpointInfo.getServiceInfoCollection()) { + if (serviceInfo.getServiceDescriptor().equals(serviceDescriptor)) { + results.add(new HubDiscoveryInfo(endpointInfo, serviceInfo)); + } + } + } + return results; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Set a callback to receive messages from the context hub * @@ -1009,6 +1074,80 @@ public final class ContextHubManager { return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId); } + /** + * Registers an endpoint and its callback with the Context Hub Service. + * + *

An endpoint is registered with the Context Hub Service and published to the HAL. When the + * registration succeeds, the endpoint can receive notifications through the provided callback. + * + * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public void registerEndpoint(@NonNull HubEndpoint hubEndpoint) { + hubEndpoint.register(mService); + } + + /** + * Use a registered endpoint to connect to another endpoint (destination) without specifying a + * service. + * + *

Context Hub Service will create the endpoint session and notify the registered endpoint. + * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * object regarding the lifecycle events of the session. + * + * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link + * ContextHubManager#registerEndpoint(HubEndpoint)}. + * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous + * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}). + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public void openSession( + @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) { + hubEndpoint.openSession(destination, null); + } + + /** + * Use a registered endpoint to connect to another endpoint (destination) for a service + * described by a {@link HubServiceInfo} object. + * + *

Context Hub Service will create the endpoint session and notify the registered endpoint. + * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * object regarding the lifecycle events of the session. + * + * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link + * ContextHubManager#registerEndpoint(HubEndpoint)}. + * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous + * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}). + * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with + * this session. The information will be sent to the destination as part of open request. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public void openSession( + @NonNull HubEndpoint hubEndpoint, + @NonNull HubEndpointInfo destination, + @NonNull HubServiceInfo serviceInfo) { + hubEndpoint.openSession(destination, serviceInfo); + } + + /** + * Unregisters an endpoint and its callback with the Context Hub Service. + * + *

An endpoint is unregistered from the HAL. The endpoint object will no longer receive + * notification through the provided callback. + * + * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}. This + * should match a previously registered object via {@link + * ContextHubManager#registerEndpoint(HubEndpoint)}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public void unregisterEndpoint(@NonNull HubEndpoint hubEndpoint) { + hubEndpoint.unregister(); + } + /** * Queries for the list of preloaded nanoapp IDs on the system. * @@ -1168,6 +1307,7 @@ public final class ContextHubManager { requireNonNull(mainLooper, "mainLooper cannot be null"); mService = service; mMainLooper = mainLooper; + try { mService.registerCallback(mClientCallback); } catch (RemoteException e) { diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java index bd87b5cb6d5431d06961cf0083d3d289d66d5a1a..ee55f81bb2a7308961fc5d6cc21d2923ca15d95a 100644 --- a/core/java/android/hardware/location/ContextHubTransaction.java +++ b/core/java/android/hardware/location/ContextHubTransaction.java @@ -51,18 +51,23 @@ public class ContextHubTransaction { /** * Constants describing the type of a transaction through the Context Hub Service. - * {@hide} + * + * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "TYPE_" }, value = { - TYPE_LOAD_NANOAPP, - TYPE_UNLOAD_NANOAPP, - TYPE_ENABLE_NANOAPP, - TYPE_DISABLE_NANOAPP, - TYPE_QUERY_NANOAPPS, - TYPE_RELIABLE_MESSAGE, - }) - public @interface Type { } + @IntDef( + prefix = {"TYPE_"}, + value = { + TYPE_LOAD_NANOAPP, + TYPE_UNLOAD_NANOAPP, + TYPE_ENABLE_NANOAPP, + TYPE_DISABLE_NANOAPP, + TYPE_QUERY_NANOAPPS, + TYPE_RELIABLE_MESSAGE, + TYPE_HUB_MESSAGE_DEFAULT, + TYPE_HUB_MESSAGE_REQUIRES_RESPONSE, + }) + public @interface Type {} public static final int TYPE_LOAD_NANOAPP = 0; public static final int TYPE_UNLOAD_NANOAPP = 1; @@ -71,24 +76,34 @@ public class ContextHubTransaction { public static final int TYPE_QUERY_NANOAPPS = 4; public static final int TYPE_RELIABLE_MESSAGE = 5; + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public static final int TYPE_HUB_MESSAGE_DEFAULT = 6; + + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public static final int TYPE_HUB_MESSAGE_REQUIRES_RESPONSE = 7; + /** * Constants describing the result of a transaction or request through the Context Hub Service. - * {@hide} + * + * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "RESULT_" }, value = { - RESULT_SUCCESS, - RESULT_FAILED_UNKNOWN, - RESULT_FAILED_BAD_PARAMS, - RESULT_FAILED_UNINITIALIZED, - RESULT_FAILED_BUSY, - RESULT_FAILED_AT_HUB, - RESULT_FAILED_TIMEOUT, - RESULT_FAILED_SERVICE_INTERNAL_FAILURE, - RESULT_FAILED_HAL_UNAVAILABLE, - RESULT_FAILED_NOT_SUPPORTED, - }) + @IntDef( + prefix = {"RESULT_"}, + value = { + RESULT_SUCCESS, + RESULT_FAILED_UNKNOWN, + RESULT_FAILED_BAD_PARAMS, + RESULT_FAILED_UNINITIALIZED, + RESULT_FAILED_BUSY, + RESULT_FAILED_AT_HUB, + RESULT_FAILED_TIMEOUT, + RESULT_FAILED_SERVICE_INTERNAL_FAILURE, + RESULT_FAILED_HAL_UNAVAILABLE, + RESULT_FAILED_NOT_SUPPORTED, + }) public @interface Result {} + public static final int RESULT_SUCCESS = 0; /** * Generic failure mode. @@ -143,7 +158,8 @@ public class ContextHubTransaction { */ private R mContents; - Response(@ContextHubTransaction.Result int result, R contents) { + /** @hide */ + public Response(@ContextHubTransaction.Result int result, R contents) { mResult = result; mContents = contents; } @@ -206,7 +222,8 @@ public class ContextHubTransaction { */ private boolean mIsResponseSet = false; - ContextHubTransaction(@Type int type) { + /** @hide */ + public ContextHubTransaction(@Type int type) { mTransactionType = type; } @@ -338,16 +355,16 @@ public class ContextHubTransaction { /** * Sets the response of the transaction. * - * This method should only be invoked by ContextHubManager as a result of a callback from - * the Context Hub Service indicating the response from a transaction. This method should not be + *

This method should only be invoked by ContextHubManager as a result of a callback from the + * Context Hub Service indicating the response from a transaction. This method should not be * invoked more than once. * * @param response the response to set - * * @throws IllegalStateException if this method is invoked multiple times * @throws NullPointerException if the response is null + * @hide */ - /* package */ void setResponse(ContextHubTransaction.Response response) { + public void setResponse(ContextHubTransaction.Response response) { synchronized (this) { Objects.requireNonNull(response, "Response cannot be null"); if (mIsResponseSet) { diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index b0cc763dc8fd1615a12fb6c88a257e0ee12eaf51..f9f41244603880e7ca58372c3b216854e5e5a3da 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -18,17 +18,20 @@ package android.hardware.location; // Declare any non-default types here with import statements import android.app.PendingIntent; -import android.hardware.location.HubInfo; +import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.IContextHubEndpoint; +import android.hardware.contexthub.IContextHubEndpointCallback; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; -import android.hardware.location.NanoApp; -import android.hardware.location.NanoAppBinary; -import android.hardware.location.NanoAppFilter; -import android.hardware.location.NanoAppInstanceInfo; +import android.hardware.location.HubInfo; import android.hardware.location.IContextHubCallback; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; import android.hardware.location.IContextHubTransactionCallback; +import android.hardware.location.NanoApp; +import android.hardware.location.NanoAppBinary; +import android.hardware.location.NanoAppFilter; +import android.hardware.location.NanoAppInstanceInfo; /** * @hide @@ -122,4 +125,16 @@ interface IContextHubService { // Enables or disables test mode @EnforcePermission("ACCESS_CONTEXT_HUB") boolean setTestMode(in boolean enable); + + // Finds all endpoints that has a specific ID + @EnforcePermission("ACCESS_CONTEXT_HUB") + List findEndpoints(long endpointId); + + // Finds all endpoints that has a specific service + @EnforcePermission("ACCESS_CONTEXT_HUB") + List findEndpointsWithService(String service); + + // Register an endpoint with the context hub + @EnforcePermission("ACCESS_CONTEXT_HUB") + IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback); } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 92608d048135185acacd805193e364e085f52f64..d2e232a946222cdc2a525ca1d20fad95e1cb983f 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -54,6 +54,11 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -823,6 +828,216 @@ public class UsbManager { } } + /** + * Opens the handle for accessory, marks it as input or output, and adds it to the map + * if it is the first time the accessory has had an I/O stream associated with it. + */ + private AccessoryHandle openHandleForAccessory(UsbAccessory accessory, + boolean openingInputStream) + throws RemoteException { + synchronized (mAccessoryHandleMapLock) { + if (mAccessoryHandleMap == null) { + mAccessoryHandleMap = new ArrayMap<>(); + } + + // If accessory isn't available in map + if (!mAccessoryHandleMap.containsKey(accessory)) { + // open accessory and store associated AccessoryHandle in map + ParcelFileDescriptor pfd = mService.openAccessory(accessory); + AccessoryHandle newHandle = new AccessoryHandle(pfd, openingInputStream, + !openingInputStream); + mAccessoryHandleMap.put(accessory, newHandle); + + return newHandle; + } + + // if accessory is already in map, get modified handle + AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory); + if (currentHandle == null) { + throw new IllegalStateException("Accessory doesn't have an associated handle yet!"); + } + + AccessoryHandle modifiedHandle = getModifiedHandleForOpeningStream( + openingInputStream, currentHandle); + + mAccessoryHandleMap.put(accessory, modifiedHandle); + + return modifiedHandle; + } + } + + private AccessoryHandle getModifiedHandleForOpeningStream(boolean openingInputStream, + @NonNull AccessoryHandle currentHandle) { + if (currentHandle.isInputStreamOpened() && openingInputStream) { + throw new IllegalStateException("Input stream already open for this accessory! " + + "Please close the existing input stream before opening a new one."); + } + + if (currentHandle.isOutputStreamOpened() && !openingInputStream) { + throw new IllegalStateException("Output stream already open for this accessory! " + + "Please close the existing output stream before opening a new one."); + } + + boolean isInputStreamOpened = openingInputStream || currentHandle.isInputStreamOpened(); + boolean isOutputStreamOpened = !openingInputStream || currentHandle.isOutputStreamOpened(); + + return new AccessoryHandle( + currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened); + } + + /** + * Marks the handle for the given accessory closed for input or output, and closes the handle + * and removes it from the map if there are no more I/O streams associated with the handle. + */ + private void closeHandleForAccessory(UsbAccessory accessory, boolean closingInputStream) + throws IOException { + synchronized (mAccessoryHandleMapLock) { + AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory); + + if (currentHandle == null) { + throw new IllegalStateException( + "No handle has been initialised for this accessory!"); + } + + AccessoryHandle modifiedHandle = getModifiedHandleForClosingStream( + closingInputStream, currentHandle); + if (!modifiedHandle.isOpen()) { + //close handle and remove accessory handle pair from map + modifiedHandle.getPfd().close(); + mAccessoryHandleMap.remove(accessory); + } else { + mAccessoryHandleMap.put(accessory, modifiedHandle); + } + } + } + + private AccessoryHandle getModifiedHandleForClosingStream(boolean closingInputStream, + @NonNull AccessoryHandle currentHandle) { + if (!currentHandle.isInputStreamOpened() && closingInputStream) { + throw new IllegalStateException( + "Attempting to close an input stream that has not been opened " + + "for this accessory!"); + } + + if (!currentHandle.isOutputStreamOpened() && !closingInputStream) { + throw new IllegalStateException( + "Attempting to close an output stream that has not been opened " + + "for this accessory!"); + } + + boolean isInputStreamOpened = !closingInputStream && currentHandle.isInputStreamOpened(); + boolean isOutputStreamOpened = closingInputStream && currentHandle.isOutputStreamOpened(); + + return new AccessoryHandle( + currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened); + } + + /** + * An InputStream you can create on a UsbAccessory, which will + * take care of calling {@link ParcelFileDescriptor#close + * ParcelFileDescriptor.close()} for you when the stream is closed. + */ + private class AccessoryAutoCloseInputStream extends FileInputStream { + + private final ParcelFileDescriptor mPfd; + private final UsbAccessory mAccessory; + + AccessoryAutoCloseInputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + this.mAccessory = accessory; + this.mPfd = pfd; + } + + @Override + public void close() throws IOException { + /* TODO(b/377850642) : Ensure the stream is closed even if client does not + explicitly close the stream to avoid corrupt FDs*/ + super.close(); + closeHandleForAccessory(mAccessory, true); + } + + + @Override + public int read() throws IOException { + final int result = super.read(); + checkError(result); + return result; + } + + @Override + public int read(byte[] b) throws IOException { + final int result = super.read(b); + checkError(result); + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + final int result = super.read(b, off, len); + checkError(result); + return result; + } + + private void checkError(int result) throws IOException { + if (result == -1 && mPfd.canDetectErrors()) { + mPfd.checkError(); + } + } + } + + /** + * An OutputStream you can create on a UsbAccessory, which will + * take care of calling {@link ParcelFileDescriptor#close + * ParcelFileDescriptor.close()} for you when the stream is closed. + */ + private class AccessoryAutoCloseOutputStream extends FileOutputStream { + private final UsbAccessory mAccessory; + + AccessoryAutoCloseOutputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) { + super(pfd.getFileDescriptor()); + mAccessory = accessory; + } + + @Override + public void close() throws IOException { + /* TODO(b/377850642) : Ensure the stream is closed even if client does not + explicitly close the stream to avoid corrupt FDs*/ + super.close(); + closeHandleForAccessory(mAccessory, false); + } + } + + /** + * Holds file descriptor and marks whether input and output streams have been opened for it. + */ + private static class AccessoryHandle { + private final ParcelFileDescriptor mPfd; + private final boolean mInputStreamOpened; + private final boolean mOutputStreamOpened; + AccessoryHandle(ParcelFileDescriptor parcelFileDescriptor, + boolean inputStreamOpened, boolean outputStreamOpened) { + mPfd = parcelFileDescriptor; + mInputStreamOpened = inputStreamOpened; + mOutputStreamOpened = outputStreamOpened; + } + + public ParcelFileDescriptor getPfd() { + return mPfd; + } + + public boolean isInputStreamOpened() { + return mInputStreamOpened; + } + + public boolean isOutputStreamOpened() { + return mOutputStreamOpened; + } + + public boolean isOpen() { + return (mInputStreamOpened || mOutputStreamOpened); + } + } + private final Context mContext; private final IUsbManager mService; private final Object mDisplayPortListenersLock = new Object(); @@ -831,6 +1046,11 @@ public class UsbManager { @GuardedBy("mDisplayPortListenersLock") private DisplayPortAltModeInfoDispatchingListener mDisplayPortServiceListener; + private final Object mAccessoryHandleMapLock = new Object(); + @GuardedBy("mAccessoryHandleMapLock") + private ArrayMap mAccessoryHandleMap; + + /** * @hide */ @@ -922,6 +1142,10 @@ public class UsbManager { * data of a USB transfer should be read at once. If only a partial request is read the rest of * the transfer is dropped. * + *

It is strongly recommended to use newer methods instead of this method, + * since this method may provide sub-optimal performance on some devices. + * This method could potentially face interim performance degradation as well. + * * @param accessory the USB accessory to open * @return file descriptor, or null if the accessory could not be opened. */ @@ -934,6 +1158,49 @@ public class UsbManager { } } + /** + * Opens an input stream for reading from the USB accessory. + * If accessory is not open at this point, accessory will first be opened. + *

If data is read from the created {@link java.io.InputStream} all + * data of a USB transfer should be read at once. If only a partial request is read, the rest of + * the transfer is dropped. + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @param accessory the USB accessory to open an input stream for + * @return input stream to read from given USB accessory + */ + @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API) + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) + public @NonNull InputStream openAccessoryInputStream(@NonNull UsbAccessory accessory) { + try { + return new AccessoryAutoCloseInputStream(accessory, + openHandleForAccessory(accessory, true).getPfd()); + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Opens an output stream for writing to the USB accessory. + * If accessory is not open at this point, accessory will first be opened. + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @param accessory the USB accessory to open an output stream for + * @return output stream to write to given USB accessory + */ + @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API) + @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY) + public @NonNull OutputStream openAccessoryOutputStream(@NonNull UsbAccessory accessory) { + try { + return new AccessoryAutoCloseOutputStream(accessory, + openHandleForAccessory(accessory, false).getPfd()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + /** * Gets the functionfs control file descriptor for the given function, with * the usb descriptors and strings already written. The file descriptor is used @@ -1293,7 +1560,7 @@ public class UsbManager { *

* This function returns the current USB bandwidth through USB Gadget HAL. * It should be used when Android device is in USB peripheral mode and - * connects to a USB host. If USB state is not configued, API will return + * connects to a USB host. If USB state is not configured, API will return * {@value #USB_DATA_TRANSFER_RATE_UNKNOWN}. In addition, the unit of the * return value is Mbps. *

diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig index 3b7a9e95c52177d04e1f4545dc67c2169f315e98..b719a7c6daacede3ffcb0ec684417324d179c161 100644 --- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig +++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig @@ -31,3 +31,11 @@ flag { description: "Feature flag to enable exposing usb speed system api" bug: "373653182" } + +flag { + name: "enable_accessory_stream_api" + is_exported: true + namespace: "usb" + description: "Feature flag to enable stream APIs for Accessory mode" + bug: "369356693" +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c3f0ef080392109c4e015d0a7ff22e37ee9f0d1..dadb5c386b76965ccdc41ef1f896c7fc3474e0e2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER; import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; +import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -3436,7 +3437,7 @@ public class InputMethodService extends AbstractInputMethodService { initialize(); mInlineSuggestionSessionController.notifyOnStartInput( editorInfo == null ? null : editorInfo.packageName, - editorInfo == null ? null : editorInfo.autofillId); + editorInfo == null ? null : editorInfo.getAutofillId()); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(editorInfo, restarting); if (mDecorViewVisible) { @@ -4391,6 +4392,39 @@ public class InputMethodService extends AbstractInputMethodService { return mNavigationBarController.isShown(); } + /** + * Called when the requested visibility of a custom IME Switcher button changes. + * + *

When the system provides an IME navigation bar, it may decide to show an IME Switcher + * button inside this bar. However, the IME can request hiding the bar provided by the system + * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides + * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful, + * then it becomes the IME's responsibility to provide a custom IME Switcher button in its + * input view, with equivalent functionality.

+ * + *

This custom button is only requested to be visible when the system provides the IME + * navigation bar, both the bar and the IME Switcher button inside it should be visible, + * but the IME successfully requested to hide the bar. This does not depend on the current + * visibility of the IME. It could be called with {@code true} while the IME is hidden, in + * which case the IME should prepare to show the button as soon as the IME itself is shown.

+ * + *

This is only called when the requested visibility changes. The default value is + * {@code false} and as such, this will not be called initially if the resulting value is + * {@code false}.

+ * + *

This can be called at any time after {@link #onCreate}, even if the IME is not currently + * visible. However, this is not guaranteed to be called before the IME is shown, as it depends + * on when the IME requested hiding the IME navigation bar. If the request is sent during + * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after + * {@link #onWindowShown}, but before the first IME frame is drawn.

+ * + * @param visible whether the button is requested visible or not. + */ + @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API) + public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) { + // Intentionally empty + } + /** * Called when the IME switch button was clicked from the client. Depending on the number of * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index b08454dd7f8fc5a938c7530436e454c8b19c939c..38be8d9f772df1eee3a362f913a3205d4cfbee46 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -41,6 +41,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -178,6 +179,9 @@ final class NavigationBarController { private boolean mDrawLegacyNavigationBarBackground; + /** Whether a custom IME Switcher button should be visible. */ + private boolean mCustomImeSwitcherVisible; + private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; @@ -265,6 +269,7 @@ final class NavigationBarController { // IME navigation bar. boolean visible = insets.isVisible(captionBar()); mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); + checkCustomImeSwitcherVisibility(); } return view.onApplyWindowInsets(insets); }); @@ -491,6 +496,8 @@ final class NavigationBarController { mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + checkCustomImeSwitcherVisibility(); + mService.mWindow.getWindow().getDecorView().getWindowInsetsController() .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar)); @@ -616,12 +623,33 @@ final class NavigationBarController { && mNavigationBarFrame.getVisibility() == View.VISIBLE; } + /** + * Checks if a custom IME Switcher button should be visible, and notifies the IME when this + * state changes. This can only be {@code true} if three conditions are met: + * + *
  • The IME should draw the IME navigation bar.
  • + *
  • The IME Switcher button should be visible when the IME is visible.
  • + *
  • The IME navigation bar should be visible, but was requested hidden by the IME.
  • + */ + private void checkCustomImeSwitcherVisibility() { + if (!Flags.imeSwitcherRevampApi()) { + return; + } + final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown + && mNavigationBarFrame != null && !isShown(); + if (visible != mCustomImeSwitcherVisible) { + mCustomImeSwitcherVisible = visible; + mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible); + } + } + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar + " mNavigationBarFrame=" + mNavigationBarFrame + " mShouldShowImeSwitcherWhenImeIsShown=" + mShouldShowImeSwitcherWhenImeIsShown + + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible + " mAppearance=0x" + Integer.toHexString(mAppearance) + " mDarkIntensity=" + mDarkIntensity + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground diff --git a/core/java/android/net/vcn/VcnFrameworkInitializer.java b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java similarity index 87% rename from core/java/android/net/vcn/VcnFrameworkInitializer.java rename to core/java/android/net/ConnectivityFrameworkInitializerBaklava.java index 8cb213b306beae5e58b5984ceab1634cac315a9e..1f0fa92d79769a4d40ea548bfdab5ae9c4c66a39 100644 --- a/core/java/android/net/vcn/VcnFrameworkInitializer.java +++ b/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java @@ -14,15 +14,21 @@ * limitations under the License. */ -package android.net.vcn; +package android.net; +import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API; + +import android.annotation.FlaggedApi; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.SystemServiceRegistry; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; import android.content.pm.PackageManager; +import android.net.vcn.IVcnManagementService; +import android.net.vcn.VcnManager; import android.os.Build; import android.os.SystemProperties; @@ -31,8 +37,9 @@ import android.os.SystemProperties; * * @hide */ -// TODO: Expose it as @SystemApi(client = MODULE_LIBRARIES) -public final class VcnFrameworkInitializer { +@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ConnectivityFrameworkInitializerBaklava { /** * Starting with {@link VANILLA_ICE_CREAM}, Telephony feature flags (e.g. {@link * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}) are being checked before returning managers @@ -55,7 +62,7 @@ public final class VcnFrameworkInitializer { */ private static final int VENDOR_API_FOR_ANDROID_V = 202404; - private VcnFrameworkInitializer() {} + private ConnectivityFrameworkInitializerBaklava() {} // Suppressing AndroidFrameworkCompatChange because we're querying vendor // partition SDK level, not application's target SDK version (which BTW we @@ -86,7 +93,10 @@ public final class VcnFrameworkInitializer { * * @throws IllegalStateException if this is called anywhere besides {@link * SystemServiceRegistry}. + * @hide */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static void registerServiceWrappers() { SystemServiceRegistry.registerContextAwareService( VcnManager.VCN_MANAGEMENT_SERVICE_STRING, diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index af93c964a8baaf89aed3542d3f304ce18b1b1e45..3219ce81c25691e78e266534a5b526b2a71fee3e 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -16,6 +16,7 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; +import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API; import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; @@ -82,7 +83,15 @@ import java.util.concurrent.TimeUnit; * */ public final class VcnGatewayConnectionConfig { - /** @hide */ + /** + * Minimum NAT timeout not set. + * + *

    When the timeout is not set, the device will automatically choose a keepalive interval and + * may reduce the keepalive frequency for power-optimization. + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + // This constant does not represent a minimum value. It indicates the value is not configured. + @SuppressLint("MinMaxConstant") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; /** @hide */ @@ -773,7 +782,7 @@ public final class VcnGatewayConnectionConfig { * * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN * Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN - * Gateway. + * Gateway; or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} to clear this value. * @return this {@link Builder} instance, for chaining */ @NonNull @@ -781,8 +790,10 @@ public final class VcnGatewayConnectionConfig { @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS) int minUdpPort4500NatTimeoutSeconds) { Preconditions.checkArgument( - minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS, - "Timeout must be at least 120s"); + minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET + || minUdpPort4500NatTimeoutSeconds + >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS, + "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET"); mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds; return this; diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java index 1fc91eea313880414abab20ebcde8354c964ab1a..3638429f33fba9ee512dbfd777ec37b89da7ddec 100644 --- a/core/java/android/net/vcn/VcnTransportInfo.java +++ b/core/java/android/net/vcn/VcnTransportInfo.java @@ -17,13 +17,16 @@ package android.net.vcn; import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; +import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API; import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS; import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.net.NetworkCapabilities; import android.net.TransportInfo; import android.net.wifi.WifiInfo; @@ -52,23 +55,29 @@ import java.util.Objects; * @hide */ // TODO: Do not store WifiInfo and subscription ID in VcnTransportInfo anymore -public class VcnTransportInfo implements TransportInfo, Parcelable { +@FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class VcnTransportInfo implements TransportInfo, Parcelable { @Nullable private final WifiInfo mWifiInfo; private final int mSubId; private final int mMinUdpPort4500NatTimeoutSeconds; + /** @hide */ public VcnTransportInfo(@NonNull WifiInfo wifiInfo) { this(wifiInfo, INVALID_SUBSCRIPTION_ID, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET); } + /** @hide */ public VcnTransportInfo(@NonNull WifiInfo wifiInfo, int minUdpPort4500NatTimeoutSeconds) { this(wifiInfo, INVALID_SUBSCRIPTION_ID, minUdpPort4500NatTimeoutSeconds); } + /** @hide */ public VcnTransportInfo(int subId) { this(null /* wifiInfo */, subId, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET); } + /** @hide */ public VcnTransportInfo(int subId, int minUdpPort4500NatTimeoutSeconds) { this(null /* wifiInfo */, subId, minUdpPort4500NatTimeoutSeconds); } @@ -86,6 +95,7 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { *

    If the underlying Network for the associated VCN is Cellular, returns null. * * @return the WifiInfo if there is an underlying WiFi connection, else null. + * @hide */ @Nullable public WifiInfo getWifiInfo() { @@ -100,17 +110,27 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { * * @return the Subscription ID if a cellular underlying Network is present, else {@link * android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * @hide */ public int getSubId() { return mSubId; } /** - * Get the VCN provided UDP port 4500 NAT timeout + * Get the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping. * - * @return the UDP 4500 NAT timeout, or + *

    To ensure uninterrupted connectivity, the device must send keepalive packets before the + * timeout. Failure to do so may result in the mapping being cleared and connection termination. + * This value is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs, + * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data. + * + * @return the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping, or * VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET if not set. + * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int) + * @hide */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public int getMinUdpPort4500NatTimeoutSeconds() { return mMinUdpPort4500NatTimeoutSeconds; } @@ -129,12 +149,21 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { && mMinUdpPort4500NatTimeoutSeconds == that.mMinUdpPort4500NatTimeoutSeconds; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override public int describeContents() { return 0; } + /** @hide */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override @NonNull public TransportInfo makeCopy(long redactions) { @@ -149,6 +178,9 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { mMinUdpPort4500NatTimeoutSeconds); } + /** @hide */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override public long getApplicableRedactions() { long redactions = REDACT_FOR_NETWORK_SETTINGS; @@ -161,7 +193,13 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { return redactions; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mSubId); @@ -174,7 +212,13 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { return "VcnTransportInfo { mWifiInfo = " + mWifiInfo + ", mSubId = " + mSubId + " }"; } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final @NonNull Creator CREATOR = new Creator() { public VcnTransportInfo createFromParcel(Parcel in) { @@ -201,37 +245,63 @@ public class VcnTransportInfo implements TransportInfo, Parcelable { } }; - /** This class can be used to construct a {@link VcnTransportInfo}. */ + /** + * This class can be used to construct a {@link VcnTransportInfo}. + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class Builder { private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET; - /** Construct Builder */ + /** + * Construct Builder + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public Builder() {} /** - * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout. + * Set the minimum duration that the VCN Gateway guarantees to preserve a NAT mapping. * *

    This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs, * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data. * - * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN - * Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN - * Gateway. + * @param minUdpPort4500NatTimeoutSeconds the minimum duration that the VCN Gateway + * guarantees to preserve a NAT mapping, or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} + * to clear this value. To ensure uninterrupted connectivity, the device must send + * keepalive packets within this interval. Failure to do so may result in the mapping + * being cleared and connection termination. * @return this {@link Builder} instance, for chaining + * @see VcnGatewayConnectionConfig.Builder#setMinUdpPort4500NatTimeoutSeconds(int) + * @hide */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public Builder setMinUdpPort4500NatTimeoutSeconds( @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS) int minUdpPort4500NatTimeoutSeconds) { Preconditions.checkArgument( - minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS, - "Timeout must be at least 120s"); + minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET + || minUdpPort4500NatTimeoutSeconds + >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS, + "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET"); mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds; return Builder.this; } - /** Build a VcnTransportInfo instance */ + /** + * Build a VcnTransportInfo instance + * + * @hide + */ + @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public VcnTransportInfo build() { return new VcnTransportInfo( diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 102bdd0b625c9d81ae0cd94d0be695f081905d16..c2e9260879a8ea87e49b38c30984929964a4279d 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -32,6 +32,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.sdk.Flags; +import android.sysprop.BackportedFixesProperties; import android.sysprop.DeviceProperties; import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; @@ -1612,12 +1613,25 @@ public class Build { * is not applicable on this device, * otherwise {@link #BACKPORTED_FIX_STATUS_UNKNOWN}. */ - @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES) public static @BackportedFixStatus int getBackportedFixStatus(long id) { - // TODO: b/308461809 - query aliases from system prop - // TODO: b/372518979 - use backported fix datastore. - return BACKPORTED_FIX_STATUS_UNKNOWN; + if (id <= 0 || id > 1023) { + return BACKPORTED_FIX_STATUS_UNKNOWN; + } + return isBitSet(BackportedFixesProperties.alias_bitset(), (int) id) + ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN; + } + + private static boolean isBitSet(List bitsetLongArray, int bitIndex) { + // Because java.util.BitSet is not threadsafe do the calculations here instead. + if (bitIndex < 0) { + return false; + } + int arrayIndex = bitIndex >> 6; + if (bitsetLongArray.size() <= arrayIndex) { + return false; + } + return (bitsetLongArray.get(arrayIndex) & (1L << bitIndex)) != 0; } /** diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index c18fb0c38b8279c072499b3381f6e1c5c1db4679..05bd10b053fe92096c1760a61eca1e5676b5e6a2 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -18,10 +18,12 @@ package android.os; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Size; @@ -72,16 +74,18 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { /** * Status when the Bundle can assert that the underlying Parcel DOES NOT contain * Binder object(s). - * * @hide */ + @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int STATUS_BINDERS_NOT_PRESENT = 0; /** * Status when the Bundle can assert that there are Binder object(s) in the Parcel. - * * @hide */ + @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int STATUS_BINDERS_PRESENT = 1; /** @@ -94,9 +98,10 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * object to the Bundle but it is not possible to assert this fact unless the Bundle is written * to a Parcel. *

    - * * @hide */ + @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int STATUS_BINDERS_UNKNOWN = 2; /** @hide */ @@ -281,7 +286,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** {@hide} */ - public void setIsIntentExtra() { + public void enableTokenVerification() { mFlags |= FLAG_VERIFY_TOKENS_PRESENT; } @@ -417,6 +422,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * Returns a status indicating whether the bundle contains any parcelled Binder objects. * @hide */ + @FlaggedApi(Flags.FLAG_ENABLE_HAS_BINDERS) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public @HasBinderStatus int hasBinders() { if ((mFlags & FLAG_HAS_BINDERS_KNOWN) != 0) { if ((mFlags & FLAG_HAS_BINDERS) != 0) { diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 69bd6685bcac24bd770e917ecb793be8aed2f829..036ccd84a600dd40fad8ebce3f11e65a593b7be9 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -19,6 +19,8 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; +import android.app.ActivityThread; +import android.app.Instrumentation; import android.compat.annotation.UnsupportedAppUsage; import android.os.Process; import android.os.UserHandle; @@ -110,11 +112,39 @@ public final class MessageQueue { private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { + // Concurrent mode modifies behavior that is observable via reflection and is commonly used + // by tests. + // For now, we limit it to system processes to avoid breaking apps and their tests. mUseConcurrent = UserHandle.isCore(Process.myUid()); + // Even then, we don't use it if instrumentation is loaded as it breaks some + // platform tests. + final Instrumentation instrumentation = getInstrumentation(); + mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting(); + // We can lift this restriction in the future after we've made it possible for test authors + // to test Looper and MessageQueue without resorting to reflection. + + // Holdback study. + if (mUseConcurrent && Flags.messageQueueForceLegacy()) { + mUseConcurrent = false; + } + mQuitAllowed = quitAllowed; mPtr = nativeInit(); } + @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class) + private static Instrumentation getInstrumentation() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread != null) { + return activityThread.getInstrumentation(); + } + return null; + } + + private static Instrumentation getInstrumentation$ravenwood() { + return null; // Instrumentation not supported on Ravenwood yet. + } + @Override protected void finalize() throws Throwable { try { diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java new file mode 100644 index 0000000000000000000000000000000000000000..f0d4f7d8737fadf080c20e929853ee8c4bd7fcb4 --- /dev/null +++ b/core/java/android/os/CpuHeadroomParams.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.health.SystemHealthManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}. + */ +@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS) +public final class CpuHeadroomParams { + final CpuHeadroomParamsInternal mInternal; + + public CpuHeadroomParams() { + mInternal = new CpuHeadroomParamsInternal(); + } + + /** @hide */ + @IntDef(flag = false, prefix = {"CPU_HEADROOM_CALCULATION_TYPE_"}, value = { + CPU_HEADROOM_CALCULATION_TYPE_MIN, // 0 + CPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CpuHeadroomCalculationType { + } + + /** + * Calculates the headroom based on minimum value over a device-defined window. + */ + public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; + + /** + * Calculates the headroom based on average value over a device-defined window. + */ + public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; + + /** + * Sets the headroom calculation type. + *

    + * + * @throws IllegalArgumentException if the type is invalid. + */ + public void setCalculationType(@CpuHeadroomCalculationType int calculationType) { + switch (calculationType) { + case CPU_HEADROOM_CALCULATION_TYPE_MIN: + case CPU_HEADROOM_CALCULATION_TYPE_AVERAGE: + mInternal.calculationType = (byte) calculationType; + return; + } + throw new IllegalArgumentException("Invalid calculation type: " + calculationType); + } + + /** + * Gets the headroom calculation type. + * Default to {@link #CPU_HEADROOM_CALCULATION_TYPE_MIN} if not set. + */ + public @CpuHeadroomCalculationType int getCalculationType() { + @CpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) { + case CPU_HEADROOM_CALCULATION_TYPE_MIN, CPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> + mInternal.calculationType; + default -> CPU_HEADROOM_CALCULATION_TYPE_MIN; + }; + return validatedType; + } + + /** + * @hide + */ + public CpuHeadroomParamsInternal getInternal() { + return mInternal; + } +} diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6cc4699a809e1705bed50fd3cb663e832177dbd0 --- /dev/null +++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.hardware.power.CpuHeadroomParams; + +/** + * Changes should be synced with match function of HintManagerService#CpuHeadroomCacheItem. + * {@hide} + */ +@JavaDerive(equals = true, toString = true) +parcelable CpuHeadroomParamsInternal { + boolean usesDeviceHeadroom = false; + CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN; + CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL; +} + diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java new file mode 100644 index 0000000000000000000000000000000000000000..efb2a28ad2b56350a6eb9a35e6ef1f466617180b --- /dev/null +++ b/core/java/android/os/GpuHeadroomParams.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.health.SystemHealthManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Headroom request params used by {@link SystemHealthManager#getGpuHeadroom(GpuHeadroomParams)}. + */ +@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS) +public final class GpuHeadroomParams { + final GpuHeadroomParamsInternal mInternal; + + public GpuHeadroomParams() { + mInternal = new GpuHeadroomParamsInternal(); + } + + /** @hide */ + @IntDef(flag = false, prefix = {"GPU_HEADROOM_CALCULATION_TYPE_"}, value = { + GPU_HEADROOM_CALCULATION_TYPE_MIN, // 0 + GPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface GpuHeadroomCalculationType { + } + + /** + * Calculates the headroom based on minimum value over a device-defined window. + */ + public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; + + /** + * Calculates the headroom based on average value over a device-defined window. + */ + public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; + + /** + * Sets the headroom calculation type. + *

    + * + * @throws IllegalArgumentException if the type is invalid. + */ + public void setCalculationType(@GpuHeadroomCalculationType int calculationType) { + switch (calculationType) { + case GPU_HEADROOM_CALCULATION_TYPE_MIN: + case GPU_HEADROOM_CALCULATION_TYPE_AVERAGE: + mInternal.calculationType = (byte) calculationType; + return; + } + throw new IllegalArgumentException("Invalid calculation type: " + calculationType); + } + + /** + * Gets the headroom calculation type. + * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set. + */ + public @GpuHeadroomCalculationType int getCalculationType() { + @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) { + case GPU_HEADROOM_CALCULATION_TYPE_MIN, GPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> + mInternal.calculationType; + default -> GPU_HEADROOM_CALCULATION_TYPE_MIN; + }; + return validatedType; + } + + /** + * @hide + */ + public GpuHeadroomParamsInternal getInternal() { + return mInternal; + } +} diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl new file mode 100644 index 0000000000000000000000000000000000000000..20309e7673f20b37b41dd3b1cfb55572ebee0c9f --- /dev/null +++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.hardware.power.GpuHeadroomParams; + +/** + * Changes should be synced with match function of HintManagerService#GpuHeadroomCacheItem. + * {@hide} + */ +@JavaDerive(equals = true, toString = true) +parcelable GpuHeadroomParamsInternal { + GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN; +} diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl index 73cdd5682f31fba7781abca6c9b362a0b6e3e5c3..33120556339f42a4f31db42e0df62b63d58062e9 100644 --- a/core/java/android/os/IHintManager.aidl +++ b/core/java/android/os/IHintManager.aidl @@ -17,6 +17,8 @@ package android.os; +import android.os.CpuHeadroomParamsInternal; +import android.os.GpuHeadroomParamsInternal; import android.os.IHintSession; import android.hardware.power.ChannelConfig; import android.hardware.power.SessionConfig; @@ -50,4 +52,8 @@ interface IHintManager { */ @nullable ChannelConfig getSessionChannel(in IBinder token); oneway void closeSessionChannel(); + float[] getCpuHeadroom(in CpuHeadroomParamsInternal params); + long getCpuHeadroomMinIntervalMillis(); + float getGpuHeadroom(in GpuHeadroomParamsInternal params); + long getGpuHeadroomMinIntervalMillis(); } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 590ddb404b63a545fbe28522ecac5a982142cce6..e63b6648a9efd6bd3e592abb7f3f4f2fc988f6fb 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -78,6 +78,9 @@ per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS # PermissionEnforcer per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com +# RemoteCallbackList +per-file RemoteCallbackList.java = shayba@google.com + # ART per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS @@ -125,3 +128,6 @@ per-file StatsServiceManager.java = file:/services/core/java/com/android/server/ # Dropbox per-file DropBoxManager* = mwachens@google.com + +# Flags +per-file flags.aconfig = file:/FF_LEADS_OWNERS diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f7285523c01a96b21addb2f9b623ffcb895f59bf..bf7116d6a05bc8564e7c15238e169f8599059545 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1371,7 +1371,6 @@ public final class Parcel { writeInt(N); if (DEBUG_ARRAY_MAP) { RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Log.d(TAG, "Writing " + N + " ArrayMap entries", here); } int startPos; diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 29ccb85c0babbe6ed86e80373fd2d1110de5c976..9435f4d59a2c96dd17a713d5e5f73a893d088d21 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -97,18 +97,6 @@ public abstract class PowerManagerInternal { return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING; } - /** - * Used by the window manager to override the screen brightness based on the - * current foreground activity. - * - * This method must only be called by the window manager. - * - * @param brightness The overridden brightness, or Float.NaN to disable the override. - * @param tag Source identifier of the app window that requests the override. - */ - public abstract void setScreenBrightnessOverrideFromWindowManager( - float brightness, CharSequence tag); - /** * Used by the window manager to override the user activity timeout based on the * current foreground activity. It can only be used to make the timeout shorter diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 01b1e5e113326bf0f4fab63661efafa5179101d2..91c482faf7d753cb4581e6e5166c7b70bc535f61 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -194,15 +194,27 @@ public class RemoteCallbackList { } } - public void maybeSubscribeToFrozenCallback() throws RemoteException { + void maybeSubscribeToFrozenCallback() throws RemoteException { if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.addFrozenStateChangeCallback(this); + try { + mBinder.addFrozenStateChangeCallback(this); + } catch (UnsupportedOperationException e) { + // The kernel does not support frozen notifications. In this case we want to + // silently fall back to FROZEN_CALLEE_POLICY_UNSET. This is done by simply + // ignoring the error and moving on. mCurrentState would always be + // STATE_UNFROZEN and all callbacks are invoked immediately. + } } } - public void maybeUnsubscribeFromFrozenCallback() { + void maybeUnsubscribeFromFrozenCallback() { if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.removeFrozenStateChangeCallback(this); + try { + mBinder.removeFrozenStateChangeCallback(this); + } catch (UnsupportedOperationException e) { + // The kernel does not support frozen notifications. Ignore the error and move + // on. + } } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 4bc8fe0a974cd80faf2791ff44139cc305868b39..5a53bc1552b84ce43a1316c8205ffd423029b457 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3928,9 +3928,9 @@ public class UserManager { final int callingUid = Binder.getCallingUid(); final int processUid = Process.myUid(); - if (Build.isDebuggable() && callingUid != processUid) { - Log.w(TAG, "Uid " + processUid + " is fetching a copy of UserProperties on" - + " behalf of callingUid " + callingUid + ". Possibly" + if (processUid == Process.SYSTEM_UID && callingUid != processUid) { + Log.w(TAG, "The System (uid " + processUid + ") is fetching a copy of" + + " UserProperties on behalf of callingUid " + callingUid + ". Possibly" + " it should carefully first clearCallingIdentity or perhaps use" + " UserManagerInternal.getUserProperties() instead?", new Throwable()); @@ -5308,7 +5308,13 @@ public class UserManager { Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS, Manifest.permission.QUERY_USERS}, conditional = true) + @CachedProperty(api = "user_manager_user_data") public List getProfiles(@UserIdInt int userId) { + if (android.multiuser.Flags.cacheProfilesReadOnly()) { + return UserManagerCache.getProfiles( + (Integer userIdentifier) -> mService.getProfiles(userIdentifier, false), + userId); + } try { return mService.getProfiles(userId, false /* enabledOnly */); } catch (RemoteException re) { @@ -6483,6 +6489,19 @@ public class UserManager { } } + /** + * This method is used to invalidate caches, when UserManagerService.mUsers + * {@link UserManagerService.UserData} is modified, including changes to {@link UserInfo}. + * In practice we determine modification by when that data is persisted, or scheduled to be + * presisted, to xml. + * @hide + */ + public static final void invalidateCacheOnUserDataChanged() { + if (android.multiuser.Flags.cacheProfilesReadOnly()) { + UserManagerCache.invalidateProfiles(); + } + } + /** * Returns a serial number on this device for a given userId. User handles can be recycled * when deleting and creating users, but serial numbers are not reused until the device is diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0cffd9f990fd70be485094a115ec57cafe2c808f..f6bc3894be4bad0f9d14cd634e209694149edc39 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -41,6 +41,7 @@ import android.os.vibrator.PwleSegment; import android.os.vibrator.RampSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationEffectSegment; +import android.os.vibrator.VibratorEnvelopeEffectInfo; import android.os.vibrator.VibratorFrequencyProfileLegacy; import android.util.MathUtils; @@ -1483,6 +1484,15 @@ public abstract class VibrationEffect implements Parcelable { public @interface PrimitiveType { } + /** @hide */ + @IntDef(prefix = { "DELAY_TYPE_" }, value = { + DELAY_TYPE_PAUSE, + DELAY_TYPE_RELATIVE_START_OFFSET, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DelayType { + } + /** * Exception thrown when adding an element to a {@link Composition} that already ends in an * indefinitely repeating effect. @@ -1541,6 +1551,53 @@ public abstract class VibrationEffect implements Parcelable { // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK public static final int PRIMITIVE_LOW_TICK = 8; + /** + * The delay represents a pause in the composition between the end of the previous primitive + * and the beginning of the next one. + * + *

    The primitive will start after the requested pause after the last primitive ended. + * The actual time the primitive will be played depends on the previous primitive's actual + * duration on the device hardware. This enables the combination of primitives to create + * more complex effects based on how close to each other they'll play. Here is an example: + * + *

    +         *     VibrationEffect popEffect = VibrationEffect.startComposition()
    +         *         .addPrimitive(PRIMITIVE_QUICK_RISE)
    +         *         .addPrimitive(PRIMITIVE_CLICK, 0.7, 50, DELAY_TYPE_PAUSE)
    +         *         .compose()
    +         * 
    + */ + @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) + public static final int DELAY_TYPE_PAUSE = 0; + + /** + * The delay represents an offset before starting this primitive, relative to the start + * time of the previous primitive in the composition. + * + *

    The primitive will start at the requested fixed time after the last primitive started, + * independently of that primitive's actual duration on the device hardware. This enables + * precise timings of primitives within a composition, ensuring they'll be played at the + * desired intervals. Here is an example: + * + *

    +         *     VibrationEffect.startComposition()
    +         *         .addPrimitive(PRIMITIVE_CLICK, 1.0)
    +         *         .addPrimitive(PRIMITIVE_TICK, 1.0, 20, DELAY_TYPE_RELATIVE_START_OFFSET)
    +         *         .addPrimitive(PRIMITIVE_THUD, 1.0, 80, DELAY_TYPE_RELATIVE_START_OFFSET)
    +         *         .compose()
    +         * 
    + * + * Will be performed on the device as follows: + * + *
    +         *  0ms               20ms                     100ms
    +         *  PRIMITIVE_CLICK---PRIMITIVE_TICK-----------PRIMITIVE_THUD
    +         * 
    + * + *

    A primitive will be dropped from the composition if it overlaps with previous ones. + */ + @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) + public static final int DELAY_TYPE_RELATIVE_START_OFFSET = 1; private final ArrayList mSegments = new ArrayList<>(); private int mRepeatIndex = -1; @@ -1665,7 +1722,26 @@ public abstract class VibrationEffect implements Parcelable { @NonNull public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { - PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay); + return addPrimitive(primitiveId, scale, delay, PrimitiveSegment.DEFAULT_DELAY_TYPE); + } + + /** + * Add a haptic primitive to the end of the current composition. + * + * @param primitiveId The primitive to add + * @param scale The scale to apply to the intensity of the primitive. + * @param delay The amount of time in milliseconds to wait before playing this primitive, + * as defined by the given {@code delayType}. + * @param delayType The type of delay to be applied, e.g. a pause between last primitive and + * this one or a start offset. + * @return This {@link Composition} object to enable adding multiple elements in one chain. + */ + @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY) + @NonNull + public Composition addPrimitive(@PrimitiveType int primitiveId, + @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay, + @DelayType int delayType) { + PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay, delayType); primitive.validate(); return addSegment(primitive); } @@ -1733,52 +1809,20 @@ public abstract class VibrationEffect implements Parcelable { default -> Integer.toString(id); }; } - } - - /** - * Start building a waveform vibration. - * - *

    The waveform envelope builder offers more flexibility for creating waveform effects, - * allowing control over vibration amplitude and frequency via smooth transitions between - * values. The waveform will start the first transition from the vibrator off state, using - * the same frequency of the first control point. To provide a different initial vibration - * frequency, use {@link #startWaveformEnvelope(float)}. - * - *

    Note: To check whether waveform envelope effects are supported, use - * {@link Vibrator#areEnvelopeEffectsSupported()}. - * - * @see VibrationEffect.WaveformEnvelopeBuilder - */ - @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - @NonNull - public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope() { - return new WaveformEnvelopeBuilder(); - } - /** - * Start building a waveform vibration with an initial frequency. - * - *

    The waveform envelope builder offers more flexibility for creating waveform effects, - * allowing control over vibration amplitude and frequency via smooth transitions between - * values. - * - *

    This is the same as {@link #startWaveformEnvelope()}, but the waveform will start - * vibrating at given frequency, in hertz, while it transitions to the new amplitude and - * frequency of the first control point. - * - *

    Note: To check whether waveform envelope effects are supported, use - * {@link Vibrator#areEnvelopeEffectsSupported()}. - * - * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater - * than zero. - * - * @see VibrationEffect.WaveformEnvelopeBuilder - */ - @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - @NonNull - public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope( - @FloatRange(from = 0) float initialFrequencyHz) { - return new WaveformEnvelopeBuilder(initialFrequencyHz); + /** + * Convert the delay type to a human readable string for debugging. + * @param type The delay type to convert + * @return The delay type in a human readable format. + * @hide + */ + public static String delayTypeToString(@DelayType int type) { + return switch (type) { + case DELAY_TYPE_PAUSE -> "PAUSE"; + case DELAY_TYPE_RELATIVE_START_OFFSET -> "START_OFFSET"; + default -> Integer.toString(type); + }; + } } /** @@ -1792,7 +1836,7 @@ public abstract class VibrationEffect implements Parcelable { * 100ms, holds that state for 200ms, and then ramps back down over 100ms: * *

    {@code
    -     * VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
    +     * VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
          *     .addControlPoint(1.0f, 120f, 100)
          *     .addControlPoint(1.0f, 120f, 200)
          *     .addControlPoint(0.0f, 120f, 100)
    @@ -1819,27 +1863,55 @@ public abstract class VibrationEffect implements Parcelable {
          *
          * 

    You can use the following APIs to obtain these limits: *

      - *
    • Maximum envelope control points: {@link Vibrator#getMaxEnvelopeEffectSize()}
    • + *
    • Maximum envelope control points: {@link VibratorEnvelopeEffectInfo#getMaxSize()} *
    • Minimum control point duration: - * {@link Vibrator#getMinEnvelopeEffectControlPointDurationMillis()}
    • + * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()} *
    • Maximum control point duration: - * {@link Vibrator#getMaxEnvelopeEffectControlPointDurationMillis()}
    • - *
    • Maximum total effect duration: {@link Vibrator#getMaxEnvelopeEffectDurationMillis()}
    • + * {@link VibratorEnvelopeEffectInfo#getMaxControlPointDurationMillis()} + *
    • Maximum total effect duration: {@link VibratorEnvelopeEffectInfo#getMaxDurationMillis()} *
    - * - * @see VibrationEffect#startWaveformEnvelope() */ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) public static final class WaveformEnvelopeBuilder { private ArrayList mSegments = new ArrayList<>(); private float mLastAmplitude = 0f; - private float mLastFrequencyHz = 0f; + private float mLastFrequencyHz = Float.NaN; - private WaveformEnvelopeBuilder() {} + public WaveformEnvelopeBuilder() {} - private WaveformEnvelopeBuilder(float initialFrequency) { - mLastFrequencyHz = initialFrequency; + /** + * Sets the initial frequency for the waveform in Hertz. + * + *

    The effect will start vibrating at this frequency when it transitions to the + * amplitude and frequency defined by the first control point. + * + *

    The frequency must be greater than zero and within the supported range. To determine + * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating + * effects using frequencies outside this range will result in the vibration not playing. + * + * @param initialFrequencyHz The starting frequency of the vibration, in Hz. Must be + * greater than zero. + */ + @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + @SuppressWarnings("MissingGetterMatchingBuilder")// No getter to initial frequency once set. + @NonNull + public WaveformEnvelopeBuilder setInitialFrequencyHz( + @FloatRange(from = 0) float initialFrequencyHz) { + + if (mSegments.isEmpty()) { + mLastFrequencyHz = initialFrequencyHz; + } else { + PwleSegment firstSegment = mSegments.getFirst(); + mSegments.set(0, new PwleSegment( + firstSegment.getStartAmplitude(), + firstSegment.getEndAmplitude(), + initialFrequencyHz, // Update start frequency + firstSegment.getEndFrequencyHz(), + (int) firstSegment.getDuration())); + } + + return this; } /** @@ -1850,15 +1922,13 @@ public abstract class VibrationEffect implements Parcelable { * perceived intensity. It's determined by the actuator response curve. * *

    Frequency must be greater than zero and within the supported range. To determine - * the supported range, use {@link Vibrator#getFrequencyProfile()}. This method returns a - * {@link android.os.vibrator.VibratorFrequencyProfile} object, which contains the - * minimum and maximum frequencies, among other frequency-related information. Creating + * the supported range, use {@link Vibrator#getFrequencyProfile()}. Creating * effects using frequencies outside this range will result in the vibration not playing. * *

    Time specifies the duration (in milliseconds) for the vibrator to smoothly transition * from the previous control point to this new one. It must be greater than zero. To * transition as quickly as possible, use - * {@link Vibrator#getMinEnvelopeEffectControlPointDurationMillis()}. + * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()}. * * @param amplitude The amplitude value between 0 and 1, inclusive. 0 represents the * vibrator being off, and 1 represents the maximum achievable amplitude @@ -1873,7 +1943,7 @@ public abstract class VibrationEffect implements Parcelable { @FloatRange(from = 0, to = 1) float amplitude, @FloatRange(from = 0) float frequencyHz, int timeMillis) { - if (mLastFrequencyHz == 0) { + if (Float.isNaN(mLastFrequencyHz)) { mLastFrequencyHz = frequencyHz; } @@ -1894,6 +1964,7 @@ public abstract class VibrationEffect implements Parcelable { * calling this method again. * * @return The {@link VibrationEffect} resulting from the list of control points. + * @throws IllegalStateException if no control points were added to the builder. */ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) @NonNull diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 53f8a9267499340ac7ee551bbac438fcd02c362c..86209140ce51c30ef7c72380c3169147fa51d4da 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -35,6 +35,7 @@ import android.media.AudioAttributes; import android.os.vibrator.Flags; import android.os.vibrator.VendorVibrationSession; import android.os.vibrator.VibrationConfig; +import android.os.vibrator.VibratorEnvelopeEffectInfo; import android.os.vibrator.VibratorFrequencyProfile; import android.os.vibrator.VibratorFrequencyProfileLegacy; import android.util.Log; @@ -137,6 +138,9 @@ public abstract class Vibrator { @Nullable private volatile VibrationConfig mVibrationConfig; + private VibratorFrequencyProfile mVibratorFrequencyProfile; + private VibratorEnvelopeEffectInfo mVibratorEnvelopeEffectInfo; + /** * @hide to prevent subclassing from outside of the framework */ @@ -351,7 +355,11 @@ public abstract class Vibrator { return null; } - return new VibratorFrequencyProfile(frequencyProfile); + if (mVibratorFrequencyProfile == null) { + mVibratorFrequencyProfile = new VibratorFrequencyProfile(frequencyProfile); + } + + return mVibratorFrequencyProfile; } /** @@ -383,70 +391,28 @@ public abstract class Vibrator { } /** - * Retrieves the maximum duration supported for an envelope effect, in milliseconds. - * - *

    If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}), - * this value will be positive. Devices with envelope effects capabilities guarantees a - * maximum duration equivalent to the product of {@link #getMaxEnvelopeEffectSize()} and - * {@link #getMaxEnvelopeEffectControlPointDurationMillis()}. If the device does not support - * envelope effects, this method will return 0. - * - * @return The maximum duration (in milliseconds) allowed for an envelope effect, or 0 if - * envelope effects are not supported. - */ - @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - public int getMaxEnvelopeEffectDurationMillis() { - return getInfo().getMaxEnvelopeEffectDurationMillis(); - } - - /** - * Retrieves the maximum number of control points supported for an envelope effect. - * - *

    If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}), - * this value will be positive. Devices with envelope effects capabilities guarantee support - * for a minimum of 16 control points. If the device does not support envelope effects, - * this method will return 0. + * Retrieves the vibrator's capabilities and limitations for envelope effects. * - * @return the maximum number of control points allowed for an envelope effect, or 0 if - * envelope effects are not supported. - */ - @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - public int getMaxEnvelopeEffectSize() { - return getInfo().getMaxEnvelopeEffectSize(); - } - - /** - * Retrieves the minimum duration supported between two control points within an envelope - * effect, in milliseconds. + *

    These parameters can be used with {@link VibrationEffect.WaveformEnvelopeBuilder} + * to create custom envelope effects. * - *

    If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}), - * this value will be positive. Devices with envelope effects capabilities guarantee - * support for durations down to at least 20 milliseconds. If the device does - * not support envelope effects, this method will return 0. + * @return The vibrator's envelope effect information, or null if not supported. If this + * vibrator is a composite of multiple physical devices then this will return a profile + * supported in all devices, or null if the intersection is empty or not available. * - * @return the minimum allowed duration between two control points in an envelope effect, - * or 0 if envelope effects are not supported. + * @see VibrationEffect.WaveformEnvelopeBuilder */ @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - public int getMinEnvelopeEffectControlPointDurationMillis() { - return getInfo().getMinEnvelopeEffectControlPointDurationMillis(); - } + @NonNull + public VibratorEnvelopeEffectInfo getEnvelopeEffectInfo() { + if (mVibratorEnvelopeEffectInfo == null) { + mVibratorEnvelopeEffectInfo = new VibratorEnvelopeEffectInfo( + getInfo().getMaxEnvelopeEffectSize(), + getInfo().getMinEnvelopeEffectControlPointDurationMillis(), + getInfo().getMaxEnvelopeEffectControlPointDurationMillis()); + } - /** - * Retrieves the maximum duration supported between two control points within an envelope - * effect, in milliseconds. - * - *

    If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}), - * this value will be positive. Devices with envelope effects capabilities guarantee support - * for durations up to at least 1 second. If the device does not support envelope effects, - * this method will return 0. - * - * @return the maximum allowed duration between two control points in an envelope effect, - * or 0 if envelope effects are not supported. - */ - @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) - public int getMaxEnvelopeEffectControlPointDurationMillis() { - return getInfo().getMaxEnvelopeEffectControlPointDurationMillis(); + return mVibratorEnvelopeEffectInfo; } /** diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 9dec8673f0192e649cbf8e987a08fcc698c12bb4..84325a4ac70b1cb7a4bfb2a5a88d777452318d63 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -121,7 +121,7 @@ public class VibratorInfo implements Parcelable { * @param qFactor The vibrator quality factor. * @param frequencyProfileLegacy The description of the vibrator supported frequencies and max * amplitude mappings. - * @param frequencyProfile The description of the vibrator supported frequencies and + * @param frequencyProfile The description of the vibrator supported frequencies and * output acceleration mappings. * @param maxEnvelopeEffectSize The maximum number of control points supported for an * envelope effect. diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 6d4e28403908bea64b4180f85d0091e81886760e..517418a717fb041ee6448118b657ffa4f0f4130c 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -1011,13 +1011,7 @@ public class WorkSource implements Parcelable { return mTags.length > 0 ? mTags[0] : null; } - // TODO: The following three trivial getters are purely for testing and will be removed - // once we have higher level logic in place, e.g for serializing this WorkChain to a proto, - // diffing it etc. - - /** @hide */ - @VisibleForTesting public int[] getUids() { int[] uids = new int[mSize]; System.arraycopy(mUids, 0, uids, 0, mSize); @@ -1025,7 +1019,6 @@ public class WorkSource implements Parcelable { } /** @hide */ - @VisibleForTesting public String[] getTags() { String[] tags = new String[mSize]; System.arraycopy(mTags, 0, tags, 0, mSize); @@ -1033,7 +1026,6 @@ public class WorkSource implements Parcelable { } /** @hide */ - @VisibleForTesting public int getSize() { return mSize; } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index d9db28e0b3c3885fb96263125f3e9449f54298e3..9b1bf057b81574654ec1e193be7eb79a36df77e7 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -3,6 +3,15 @@ container: "system" container: "system" # keep-sorted start block=yes newline_separated=yes +flag { + # Holdback study for concurrent MessageQueue. + # Do not promote beyond trunkfood. + namespace: "system_performance" + name: "message_queue_force_legacy" + description: "Whether to holdback concurrent MessageQueue (force legacy)." + bug: "336880969" +} + flag { name: "adpf_gpu_report_actual_work_duration" is_exported: true @@ -65,6 +74,14 @@ flag { bug: "315894228" } +flag { + name: "adpf_use_load_hints" + namespace: "game" + description: "Guards use of the ADPF public load hints behind a readonly flag" + is_fixed_read_only: true + bug: "367803904" +} + flag { name: "allow_consentless_bugreport_delegated_consent" namespace: "crumpet" @@ -147,6 +164,13 @@ flag { bug: "361157077" } +flag { + name: "cpu_gpu_headrooms" + namespace: "game" + description: "Feature flag for adding CPU/GPU headroom API" + bug: "346604998" +} + flag { name: "disallow_cellular_null_ciphers_restriction" namespace: "cellular_security" @@ -178,6 +202,17 @@ flag { bug: "366598445" } +flag { + name: "material_colors_10_2024" + namespace: "systemui" + description: "Adding new Material Tokens as of October 2024" + bug: "376195115" + is_exported: true + metadata { + purpose: PURPOSE_FEATURE + } +} + flag { name: "message_queue_tail_tracking" namespace: "system_performance" @@ -251,6 +286,15 @@ flag { bug: "377557749" } +flag { + namespace: "system_performance" + name: "enable_has_binders" + is_exported: true + description: "Add hasBinders to Public API under a flag." + is_fixed_read_only: true + bug: "330345513" +} + flag { namespace: "system_performance" name: "perfetto_sdk_tracing" diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index deabfed365a6a4a5e5573de2a13badeabdc11f8b..4db9bc333e2bff9a872edf3a02fd018cdfcfa673 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -17,6 +17,7 @@ package android.os.health; import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; @@ -25,6 +26,11 @@ import android.content.Context; import android.os.BatteryStats; import android.os.Build; import android.os.Bundle; +import android.os.CpuHeadroomParams; +import android.os.CpuHeadroomParamsInternal; +import android.os.GpuHeadroomParams; +import android.os.GpuHeadroomParamsInternal; +import android.os.IHintManager; import android.os.IPowerStatsService; import android.os.OutcomeReceiver; import android.os.PowerMonitor; @@ -68,6 +74,8 @@ public class SystemHealthManager { private final IBatteryStats mBatteryStats; @Nullable private final IPowerStatsService mPowerStats; + @Nullable + private final IHintManager mHintManager; private List mPowerMonitorsInfo; private final Object mPowerMonitorsLock = new Object(); private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000; @@ -88,14 +96,111 @@ public class SystemHealthManager { public SystemHealthManager() { this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)), IPowerStatsService.Stub.asInterface( - ServiceManager.getService(Context.POWER_STATS_SERVICE))); + ServiceManager.getService(Context.POWER_STATS_SERVICE)), + IHintManager.Stub.asInterface( + ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE))); } /** {@hide} */ public SystemHealthManager(@NonNull IBatteryStats batteryStats, - @Nullable IPowerStatsService powerStats) { + @Nullable IPowerStatsService powerStats, @Nullable IHintManager hintManager) { mBatteryStats = batteryStats; mPowerStats = powerStats; + mHintManager = hintManager; + } + + /** + * Provides an estimate of global available CPU headroom of the calling thread. + *

    + * + * @param params params to customize the CPU headroom calculation, null to use default params. + * @return a single value a {@code Float.NaN} if it's temporarily unavailable. + * A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be + * granted. + * @throws UnsupportedOperationException if the API is unsupported or the request params can't + * be served. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom( + @Nullable CpuHeadroomParams params) { + if (mHintManager == null) { + throw new UnsupportedOperationException(); + } + try { + return mHintManager.getCpuHeadroom( + params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0]; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + + + /** + * Provides an estimate of global available GPU headroom of the device. + *

    + * + * @param params params to customize the GPU headroom calculation, null to use default params. + * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable. + * A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be + * granted. + * @throws UnsupportedOperationException if the API is unsupported or the request params can't + * be served. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom( + @Nullable GpuHeadroomParams params) { + if (mHintManager == null) { + throw new UnsupportedOperationException(); + } + try { + return mHintManager.getGpuHeadroom( + params != null ? params.getInternal() : new GpuHeadroomParamsInternal()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in + * milliseconds. + *

    + * The {@link #getCpuHeadroom(CpuHeadroomParams)} API may return cached result if called more + * frequent than the interval. + * + * @throws UnsupportedOperationException if the API is unsupported. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + public long getCpuHeadroomMinIntervalMillis() { + if (mHintManager == null) { + throw new UnsupportedOperationException(); + } + try { + return mHintManager.getCpuHeadroomMinIntervalMillis(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in + * milliseconds. + *

    + * The {@link #getGpuHeadroom(GpuHeadroomParams)} API may return cached result if called more + * frequent than the interval. + * + * @throws UnsupportedOperationException if the API is unsupported. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + public long getGpuHeadroomMinIntervalMillis() { + if (mHintManager == null) { + throw new UnsupportedOperationException(); + } + try { + return mHintManager.getGpuHeadroomMinIntervalMillis(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } /** @@ -261,7 +366,7 @@ public class SystemHealthManager { mPowerMonitorsInfo = result; } if (executor != null) { - executor.execute(()-> onResult.accept(result)); + executor.execute(() -> onResult.accept(result)); } else { onResult.accept(result); } diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java index 91653edd1ba57b0a56db570e9f366fec4ef3a3f8..889d735c1d39d5e440cc192548872022325b32a7 100644 --- a/core/java/android/os/vibrator/PrimitiveSegment.java +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -26,6 +26,7 @@ import android.os.VibratorInfo; import com.android.internal.util.Preconditions; +import java.util.Locale; import java.util.Objects; /** @@ -43,19 +44,29 @@ public final class PrimitiveSegment extends VibrationEffectSegment { /** @hide */ public static final int DEFAULT_DELAY_MILLIS = 0; + /** @hide */ + public static final int DEFAULT_DELAY_TYPE = VibrationEffect.Composition.DELAY_TYPE_PAUSE; + private final int mPrimitiveId; private final float mScale; private final int mDelay; + private final int mDelayType; PrimitiveSegment(@NonNull Parcel in) { - this(in.readInt(), in.readFloat(), in.readInt()); + this(in.readInt(), in.readFloat(), in.readInt(), in.readInt()); } /** @hide */ public PrimitiveSegment(int id, float scale, int delay) { + this(id, scale, delay, DEFAULT_DELAY_TYPE); + } + + /** @hide */ + public PrimitiveSegment(int id, float scale, int delay, int delayType) { mPrimitiveId = id; mScale = scale; mDelay = delay; + mDelayType = delayType; } public int getPrimitiveId() { @@ -70,6 +81,11 @@ public final class PrimitiveSegment extends VibrationEffectSegment { return mDelay; } + /** @hide */ + public int getDelayType() { + return mDelayType; + } + @Override public long getDuration() { return -1; @@ -112,8 +128,7 @@ public final class PrimitiveSegment extends VibrationEffectSegment { if (Float.compare(mScale, newScale) == 0) { return this; } - - return new PrimitiveSegment(mPrimitiveId, newScale, mDelay); + return new PrimitiveSegment(mPrimitiveId, newScale, mDelay, mDelayType); } /** @hide */ @@ -124,8 +139,7 @@ public final class PrimitiveSegment extends VibrationEffectSegment { if (Float.compare(mScale, newScale) == 0) { return this; } - - return new PrimitiveSegment(mPrimitiveId, newScale, mDelay); + return new PrimitiveSegment(mPrimitiveId, newScale, mDelay, mDelayType); } /** @hide */ @@ -142,6 +156,7 @@ public final class PrimitiveSegment extends VibrationEffectSegment { VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId"); Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale"); VibrationEffectSegment.checkDurationArgument(mDelay, "delay"); + Preconditions.checkArgument(isValidDelayType(mDelayType), "delayType"); } @Override @@ -150,6 +165,7 @@ public final class PrimitiveSegment extends VibrationEffectSegment { dest.writeInt(mPrimitiveId); dest.writeFloat(mScale); dest.writeInt(mDelay); + dest.writeInt(mDelayType); } @Override @@ -163,14 +179,16 @@ public final class PrimitiveSegment extends VibrationEffectSegment { + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId) + ", scale=" + mScale + ", delay=" + mDelay + + ", delayType=" + VibrationEffect.Composition.delayTypeToString(mDelayType) + '}'; } /** @hide */ @Override public String toDebugString() { - return String.format("Primitive=%s(scale=%.2f, delay=%dms)", - VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, mDelay); + return String.format(Locale.ROOT, "Primitive=%s(scale=%.2f, %s=%dms)", + VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, + toDelayTypeDebugString(mDelayType), mDelay); } @Override @@ -180,12 +198,28 @@ public final class PrimitiveSegment extends VibrationEffectSegment { PrimitiveSegment that = (PrimitiveSegment) o; return mPrimitiveId == that.mPrimitiveId && Float.compare(that.mScale, mScale) == 0 - && mDelay == that.mDelay; + && mDelay == that.mDelay + && mDelayType == that.mDelayType; } @Override public int hashCode() { - return Objects.hash(mPrimitiveId, mScale, mDelay); + return Objects.hash(mPrimitiveId, mScale, mDelay, mDelayType); + } + + private static boolean isValidDelayType(int delayType) { + return switch (delayType) { + case VibrationEffect.Composition.DELAY_TYPE_PAUSE, + VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET -> true; + default -> false; + }; + } + + private static String toDelayTypeDebugString(int delayType) { + return switch (delayType) { + case VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET -> "startOffset"; + default -> "pause"; + }; } @NonNull diff --git a/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..afaab55f4ed470ff0ec79f48d4abcb1aae1b0fbe --- /dev/null +++ b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.vibrator; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import java.util.Objects; + +/** + * Provides information about the vibrator hardware capabilities and limitations regarding + * waveform envelope effects. This includes: + *

      + *
    • Maximum number of control points supported. + *
    • Minimum and maximum duration for individual segments. + *
    • Maximum total duration for an envelope effect. + *
    + * + *

    This information can be used to help construct waveform envelope effects with + * {@link VibrationEffect.WaveformEnvelopeBuilder}. When designing these effects, it is also + * recommended to check the {@link VibratorFrequencyProfile} for information about the supported + * frequency range and the vibrator's output response. + * + * @see VibrationEffect.WaveformEnvelopeBuilder + * @see VibratorFrequencyProfile + */ +@FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) +public final class VibratorEnvelopeEffectInfo implements Parcelable { + private final int mMaxSize; + private final long mMinControlPointDurationMillis; + private final long mMaxControlPointDurationMillis; + + VibratorEnvelopeEffectInfo(Parcel in) { + mMaxSize = in.readInt(); + mMinControlPointDurationMillis = in.readLong(); + mMaxControlPointDurationMillis = in.readLong(); + } + + /** + * Default constructor. + * + * @param maxSize The maximum number of control points supported for an + * envelope effect. + * @param minControlPointDurationMillis The minimum duration supported between two control + * points within an envelope effect. + * @param maxControlPointDurationMillis The maximum duration supported between two control + * points within an envelope effect. + * @hide + */ + public VibratorEnvelopeEffectInfo(int maxSize, + long minControlPointDurationMillis, + long maxControlPointDurationMillis) { + mMaxSize = maxSize; + mMinControlPointDurationMillis = minControlPointDurationMillis; + mMaxControlPointDurationMillis = maxControlPointDurationMillis; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mMaxSize); + dest.writeLong(mMinControlPointDurationMillis); + dest.writeLong(mMaxControlPointDurationMillis); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VibratorEnvelopeEffectInfo)) { + return false; + } + VibratorEnvelopeEffectInfo other = (VibratorEnvelopeEffectInfo) o; + return mMaxSize == other.mMaxSize + && mMinControlPointDurationMillis == other.mMinControlPointDurationMillis + && mMaxControlPointDurationMillis == other.mMaxControlPointDurationMillis; + } + + @Override + public int hashCode() { + return Objects.hash(mMaxSize, + mMinControlPointDurationMillis, + mMaxControlPointDurationMillis); + } + + @Override + public String toString() { + return "VibratorEnvelopeEffectInfo{" + + ", mMaxSize=" + mMaxSize + + ", mMinControlPointDurationMillis=" + mMinControlPointDurationMillis + + ", mMaxControlPointDurationMillis=" + mMaxControlPointDurationMillis + + '}'; + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public VibratorEnvelopeEffectInfo createFromParcel(Parcel in) { + return new VibratorEnvelopeEffectInfo(in); + } + + @Override + public VibratorEnvelopeEffectInfo[] newArray(int size) { + return new VibratorEnvelopeEffectInfo[size]; + } + }; + + /** + * Retrieves the maximum duration supported for an envelope effect, in milliseconds. + * + *

    If the device supports envelope effects + * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be + * positive. Devices with envelope effects capabilities guarantees a maximum duration + * equivalent to the product of {@link #getMaxSize()} and + * {@link #getMaxControlPointDurationMillis()}. If the device does not support + * envelope effects, this method will return 0. + * + * @return The maximum duration (in milliseconds) allowed for an envelope effect, or 0 if + * envelope effects are not supported. + */ + public long getMaxDurationMillis() { + return mMaxSize * mMaxControlPointDurationMillis; + } + + /** + * Retrieves the maximum number of control points supported for an envelope effect. + * + *

    If the device supports envelope effects + * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be + * positive. Devices with envelope effects capabilities guarantee support for a minimum of + * 16 control points. If the device does not support envelope effects, this method will + * return 0. + * + * @return the maximum number of control points allowed for an envelope effect, or 0 if + * envelope effects are not supported. + */ + public int getMaxSize() { + return mMaxSize; + } + + /** + * Retrieves the minimum duration supported between two control points within an envelope + * effect, in milliseconds. + * + *

    If the device supports envelope effects + * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be + * positive. Devices with envelope effects capabilities guarantee support for durations down + * to at least 20 milliseconds. If the device does not support envelope effects, + * this method will return 0. + * + * @return the minimum allowed duration between two control points in an envelope effect, + * or 0 if envelope effects are not supported. + */ + public long getMinControlPointDurationMillis() { + return mMinControlPointDurationMillis; + } + + /** + * Retrieves the maximum duration supported between two control points within an envelope + * effect, in milliseconds. + * + *

    If the device supports envelope effects + * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be + * positive. Devices with envelope effects capabilities guarantee support for durations up to + * at least 1 second. If the device does not support envelope effects, this method + * will return 0. + * + * @return the maximum allowed duration between two control points in an envelope effect, + * or 0 if envelope effects are not supported. + */ + public long getMaxControlPointDurationMillis() { + return mMaxControlPointDurationMillis; + } +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 60a0ae3f107dc044529c594bf9ac5f0a4dba5a2f..0a35fe3995318d505d6ea18f006d506d92e89fd2 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -284,7 +284,7 @@ flag { is_fixed_read_only: true is_exported: true namespace: "android_health_services" - description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)" + description: "Enables replacement of BODY_SENSORS/BODY_SENSORS_BACKGROUND permissions with granular health permissions READ_HEART_RATE, READ_SKIN_TEMPERATURE, READ_OXYGEN_SATURATION, and READ_HEALTH_DATA_IN_BACKGROUND" bug: "364638912" } @@ -299,24 +299,6 @@ flag { } } -flag { - name: "platform_skin_temperature_enabled" - is_fixed_read_only: true - is_exported: true - namespace: "android_health_services" - description: "This fixed read-only flag is used to enable platform support for Skin Temperature." - bug: "369872443" -} - -flag { - name: "platform_oxygen_saturation_enabled" - is_fixed_read_only: true - is_exported: true - namespace: "android_health_services" - description: "This fixed read-only flag is used to enable platform support for Oxygen Saturation (SpO2)." - bug: "369873227" -} - flag { name: "allow_host_permission_dialogs_on_virtual_devices" is_exported: true @@ -386,3 +368,44 @@ flag { description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases." bug: "370977414" } + +flag { + name: "system_selection_toolbar_enabled" + namespace: "permissions" + description: "Enables the system selection toolbar feature." + bug: "363318732" +} + +flag { + name: "use_system_selection_toolbar_in_sysui" + namespace: "permissions" + description: "Uses the SysUi process to host the SelectionToolbarRenderService." + bug: "363318732" +} + +flag{ + name: "note_op_batching_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Batch noteOperations on the client to reduce binder call volume" + bug: "366013082" +} + +flag { + name: "supervision_role_permission_update_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "supervision" + description: "This flag is used to enable all the remaining permissions required to the supervision role" + bug: "367333883" +} + +flag { + name: "permission_request_short_circuit_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "This flag is used to short circuit the request for permananently denied permissions" + bug: "378923900" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ef351719ea70b5d290651fb9f594b27077c662aa..a35c9c1cd4ece87f7553eb9364c9ca965f6f0298 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1216,6 +1216,69 @@ public final class Settings { public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS"; + /** + * Activity Action: Show screen for allowing the region configuration. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REGION_SETTINGS = + "android.settings.REGION_SETTINGS"; + + /** + * Activity Action: Show first day of week configuration settings. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_FIRST_DAY_OF_WEEK_SETTINGS = + "android.settings.FIRST_DAY_OF_WEEK_SETTINGS"; + + /** + * Activity Action: Show temperature unit configuration settings. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_TEMPERATURE_UNIT_SETTINGS = + "android.settings.TEMPERATURE_UNIT_SETTINGS"; + + /** + * Activity Action: Show numbering system configuration settings. + *

    + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = + "android.settings.NUMBERING_SYSTEM_SETTINGS"; + + /** + * Activity Action: Show measurement system configuration settings. + *

    + * Input: Nothing. + *

    + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MEASUREMENT_SYSTEM_SETTINGS = + "android.settings.MEASUREMENT_SYSTEM_SETTINGS"; + /** * Activity Action: Show settings to allow configuration of lockscreen. *

    @@ -6332,27 +6395,6 @@ public final class Settings { public static final String SCREEN_FLASH_NOTIFICATION_COLOR = "screen_flash_notification_color_global"; - - /** - * A semi-colon separated list of Bluetooth hearing devices' local ambient volume. - * Each entry is encoded as a key=value list, separated by commas. Ex: - * - * "addr=XX:XX:XX:00:11,ambient=20,group_ambient=30;addr=XX:XX:XX:00:22,ambient=50" - * - * The following keys are supported: - *

    -         * addr                 (String)
    -         * ambient              (int)
    -         * group_ambient        (int)
    -         * control_expanded     (boolean)
    -         * 
    - * - * Each entry must contains "addr" attribute, otherwise it'll be ignored. - * @hide - */ - public static final String HEARING_DEVICE_LOCAL_AMBIENT_VOLUME = - "hearing_device_local_ambient_volume"; - /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add @@ -6497,7 +6539,6 @@ public final class Settings { PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING); PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON); - PRIVATE_SETTINGS.add(HEARING_DEVICE_LOCAL_AMBIENT_VOLUME); } /** @@ -18074,6 +18115,44 @@ public final class Settings { */ public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side"; + /** + * A semi-colon separated list of Bluetooth hearing devices' local ambient volume data. + * Each entry is encoded as a key=value list, separated by commas. Ex: + * + * "addr=XX:XX:XX:00:11,ambient=20,group_ambient=30;addr=XX:XX:XX:00:22,ambient=50" + * + * The following keys are supported: + *
    +         * addr                 (String)
    +         * ambient              (int)
    +         * group_ambient        (int)
    +         * control_expanded     (boolean)
    +         * 
    + * + * Each entry must contains "addr" attribute, otherwise it'll be ignored. + * @hide + */ + public static final String HEARING_DEVICE_LOCAL_AMBIENT_VOLUME = + "hearing_device_local_ambient_volume"; + + /** + * A semi-colon separated list of Bluetooth hearing devices' notification data. + * Each entry is encoded as a key=value list, separated by commas. Ex: + * + * "addr=XX:XX:XX:00:11,input_changes=1" + * + * The following keys are supported: + *
    +         * addr                 (String)
    +         * input_changes        (boolean)
    +         * 
    + * + * Each entry must contains "addr" attribute, otherwise it'll be ignored. + * @hide + */ + public static final String HEARING_DEVICE_LOCAL_NOTIFICATION = + "hearing_device_local_notification"; + /** * Global settings that shouldn't be persisted. * diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index 4c636735b5ce2335544d7f7d4192e1a0f1b9d279..fff53637485bf2244ee98eaf0dd249fbcd500b5a 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -63,3 +63,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "system_regional_preferences_api_enabled" + is_exported: true + namespace: "globalintl" + description: "Feature flag for regional preferences APIs" + bug: "370379000" +} diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java index 6f3e3d8f0d3b940379815d32a2109cddf98803c7..9fe0dda136d1744441d65104cff1b906968037ba 100644 --- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java +++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java @@ -16,20 +16,30 @@ package android.security.advancedprotection; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.content.Intent; import android.os.Binder; import android.os.RemoteException; import android.security.Flags; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -45,6 +55,139 @@ import java.util.concurrent.Executor; public final class AdvancedProtectionManager { private static final String TAG = "AdvancedProtectionMgr"; + /** + * Advanced Protection's identifier for setting policies or restrictions in DevicePolicyManager. + * + * @hide */ + public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY = + "android.security.advancedprotection"; + + /** + * Feature identifier for disallowing 2G. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = + "android.security.advancedprotection.feature_disallow_2g"; + + /** + * Feature identifier for disallowing install of unknown sources. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = + "android.security.advancedprotection.feature_disallow_install_unknown_sources"; + + /** + * Feature identifier for disallowing USB. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_USB = + "android.security.advancedprotection.feature_disallow_usb"; + + /** + * Feature identifier for disallowing WEP. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_WEP = + "android.security.advancedprotection.feature_disallow_wep"; + + /** + * Feature identifier for enabling MTE. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_ENABLE_MTE = + "android.security.advancedprotection.feature_enable_mte"; + + /** @hide */ + @StringDef(prefix = { "FEATURE_ID_" }, value = { + FEATURE_ID_DISALLOW_CELLULAR_2G, + FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, + FEATURE_ID_DISALLOW_USB, + FEATURE_ID_DISALLOW_WEP, + FEATURE_ID_ENABLE_MTE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FeatureId {} + + private static final Set ALL_FEATURE_IDS = Set.of( + FEATURE_ID_DISALLOW_CELLULAR_2G, + FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, + FEATURE_ID_DISALLOW_USB, + FEATURE_ID_DISALLOW_WEP, + FEATURE_ID_ENABLE_MTE); + + /** + * Activity Action: Show a dialog with disabled by advanced protection message. + *

    If a user action or a setting toggle is disabled by advanced protection, this dialog can + * be triggered to let the user know about this. + *

    + * Input: + *

    {@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier. + *

    {@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action. + *

    + * Output: Nothing. + * + * @hide */ + @SystemApi + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + @FlaggedApi(android.security.Flags.FLAG_AAPM_API) + public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = + "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; + + /** + * A string extra used with {@link #createSupportIntent} to identify the feature that needs to + * show a support dialog explaining it was disabled by advanced protection. + * + * @hide */ + @FeatureId + @SystemApi + public static final String EXTRA_SUPPORT_DIALOG_FEATURE = + "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; + + /** + * A string extra used with {@link #createSupportIntent} to identify the type of the action that + * needs to be explained in the support dialog. + * + * @hide */ + @SupportDialogType + @SystemApi + public static final String EXTRA_SUPPORT_DIALOG_TYPE = + "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE"; + + /** + * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was + * blocked by advanced protection. + * + * @hide */ + @SystemApi + public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = + "android.security.advancedprotection.type_blocked_interaction"; + + /** + * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle + * that was disabled by advanced protection. + * + * @hide */ + @SystemApi + public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = + "android.security.advancedprotection.type_disabled_setting"; + + /** @hide */ + @StringDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = { + SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, + SUPPORT_DIALOG_TYPE_DISABLED_SETTING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SupportDialogType {} + + private static final Set ALL_SUPPORT_DIALOG_TYPES = Set.of( + SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, + SUPPORT_DIALOG_TYPE_DISABLED_SETTING); + private final ConcurrentHashMap mCallbackMap = new ConcurrentHashMap<>(); @@ -163,6 +306,43 @@ public final class AdvancedProtectionManager { } } + /** + * Called by a feature to display a support dialog when a feature was disabled by advanced + * protection. This returns an intent that can be used with + * {@link Context#startActivity(Intent)} to display the dialog. + * + *

    Note that this method doesn't check if the feature is actually disabled, i.e. this method + * will always return an intent. + * + * @param featureId The feature identifier. + * @param type The type of the feature describing the action that needs to be explained + * in the dialog or null for default explanation. + * @return Intent An intent to be used to start the dialog-activity that explains a feature was + * disabled by advanced protection. + * @hide + */ + @SystemApi + public @NonNull Intent createSupportIntent(@NonNull @FeatureId String featureId, + @Nullable @SupportDialogType String type) { + Objects.requireNonNull(featureId); + if (!ALL_FEATURE_IDS.contains(featureId)) { + throw new IllegalArgumentException(featureId + " is not a valid feature ID. See" + + " FEATURE_ID_* APIs."); + } + if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) { + throw new IllegalArgumentException(type + " is not a valid type. See" + + " SUPPORT_DIALOG_TYPE_* APIs."); + } + + Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId); + if (type != null) { + intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type); + } + return intent; + } + /** * A callback class for monitoring changes to Advanced Protection state * diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 7cb0ffcfcc721f89885ab7b16739f60b264730f0..09004b3dcf0328512cc800c3b7005df9309f6e5c 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -109,11 +109,19 @@ flag { flag { name: "afl_api" - namespace: "platform_security" + namespace: "hardware_backed_security" description: "AFL feature" bug: "365994454" } +flag { + name: "protect_device_config_flags" + namespace: "psap_ai" + description: "Feature flag to limit adb shell to allowlisted flags" + bug: "364083026" + is_fixed_read_only: true +} + flag { name: "keystore_grant_api" namespace: "hardware_backed_security" diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java index 90906edcc636a2aa36070d49cae279731daebb22..3d908cca150c288e5a3e68387a23f88d9d5cc995 100644 --- a/core/java/android/security/forensic/ForensicEvent.java +++ b/core/java/android/security/forensic/ForensicEvent.java @@ -17,13 +17,17 @@ package android.security.forensic; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.ConnectEvent; +import android.app.admin.DnsEvent; +import android.app.admin.SecurityLog.SecurityEvent; import android.os.Parcel; import android.os.Parcelable; import android.security.Flags; -import android.util.ArrayMap; -import java.util.Map; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A class that represents a forensic event. @@ -33,11 +37,24 @@ import java.util.Map; public final class ForensicEvent implements Parcelable { private static final String TAG = "ForensicEvent"; - @NonNull - private final String mType; + public static final int SECURITY_EVENT = 0; + public static final int NETWORK_EVENT_DNS = 1; + public static final int NETWORK_EVENT_CONNECT = 2; - @NonNull - private final Map mKeyValuePairs; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ForensicEvent.SECURITY_EVENT, + ForensicEvent.NETWORK_EVENT_DNS, + ForensicEvent.NETWORK_EVENT_CONNECT, + }) + public @interface EventType {} + + @NonNull @EventType private final int mType; + + private final SecurityEvent mSecurityEvent; + private final DnsEvent mNetworkEventDns; + private final ConnectEvent mNetworkEventConnect; public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator<>() { @@ -50,30 +67,99 @@ public final class ForensicEvent implements Parcelable { } }; - public ForensicEvent(@NonNull String type, @NonNull Map keyValuePairs) { - mType = type; - mKeyValuePairs = keyValuePairs; + public ForensicEvent(@NonNull SecurityEvent securityEvent) { + mType = SECURITY_EVENT; + mSecurityEvent = securityEvent; + mNetworkEventDns = null; + mNetworkEventConnect = null; + } + + public ForensicEvent(@NonNull DnsEvent dnsEvent) { + mType = NETWORK_EVENT_DNS; + mNetworkEventDns = dnsEvent; + mSecurityEvent = null; + mNetworkEventConnect = null; + } + + public ForensicEvent(@NonNull ConnectEvent connectEvent) { + mType = NETWORK_EVENT_CONNECT; + mNetworkEventConnect = connectEvent; + mSecurityEvent = null; + mNetworkEventDns = null; } private ForensicEvent(@NonNull Parcel in) { - mType = in.readString(); - mKeyValuePairs = new ArrayMap<>(in.readInt()); - in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class); + mType = in.readInt(); + switch (mType) { + case SECURITY_EVENT: + mSecurityEvent = SecurityEvent.CREATOR.createFromParcel(in); + mNetworkEventDns = null; + mNetworkEventConnect = null; + break; + case NETWORK_EVENT_DNS: + mNetworkEventDns = DnsEvent.CREATOR.createFromParcel(in); + mSecurityEvent = null; + mNetworkEventConnect = null; + break; + case NETWORK_EVENT_CONNECT: + mNetworkEventConnect = ConnectEvent.CREATOR.createFromParcel(in); + mSecurityEvent = null; + mNetworkEventDns = null; + break; + default: + throw new IllegalArgumentException("Invalid event type: " + mType); + } } - public String getType() { + /** Returns the type of the forensic event. */ + @NonNull + public @EventType int getType() { return mType; } - public Map getKeyValuePairs() { - return mKeyValuePairs; + /** Returns the SecurityEvent object. */ + @NonNull + public SecurityEvent getSecurityEvent() { + if (mType == SECURITY_EVENT) { + return mSecurityEvent; + } + throw new IllegalArgumentException("Event type is not security event: " + mType); + } + + /** Returns the DnsEvent object. */ + @NonNull + public DnsEvent getDnsEvent() { + if (mType == NETWORK_EVENT_DNS) { + return mNetworkEventDns; + } + throw new IllegalArgumentException("Event type is not network DNS event: " + mType); + } + + /** Returns the ConnectEvent object. */ + @NonNull + public ConnectEvent getConnectEvent() { + if (mType == NETWORK_EVENT_CONNECT) { + return mNetworkEventConnect; + } + throw new IllegalArgumentException("Event type is not network connect event: " + mType); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeString(mType); - out.writeInt(mKeyValuePairs.size()); - out.writeMap(mKeyValuePairs); + out.writeInt(mType); + switch (mType) { + case SECURITY_EVENT: + out.writeParcelable(mSecurityEvent, flags); + break; + case NETWORK_EVENT_DNS: + out.writeParcelable(mNetworkEventDns, flags); + break; + case NETWORK_EVENT_CONNECT: + out.writeParcelable(mNetworkEventConnect, flags); + break; + default: + throw new IllegalArgumentException("Invalid event type: " + mType); + } } @FlaggedApi(Flags.FLAG_AFL_API) @@ -86,7 +172,6 @@ public final class ForensicEvent implements Parcelable { public String toString() { return "ForensicEvent{" + "mType=" + mType - + ", mKeyValuePairs=" + mKeyValuePairs + '}'; } } diff --git a/core/java/android/security/forensic/ForensicManager.java b/core/java/android/security/forensic/ForensicManager.java new file mode 100644 index 0000000000000000000000000000000000000000..9126182eda7b32cc70631c7e902de78f8088c585 --- /dev/null +++ b/core/java/android/security/forensic/ForensicManager.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.security.forensic; + +import static android.Manifest.permission.MANAGE_FORENSIC_STATE; +import static android.Manifest.permission.READ_FORENSIC_STATE; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.security.Flags; +import android.util.Log; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * ForensicManager manages the forensic logging on Android devices. + * Upon user consent, forensic logging collects various device events for + * off-device investigation of potential device compromise. + *

    + * Forensic logging can either be enabled ({@link #STATE_ENABLED} + * or disabled ({@link #STATE_DISABLED}). + *

    + * The Forensic logs will be transferred to + * {@link android.security.forensic.ForensicEventTransport}. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_AFL_API) +@SystemService(Context.FORENSIC_SERVICE) +public class ForensicManager { + private static final String TAG = "ForensicManager"; + + /** @hide */ + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = { + STATE_UNKNOWN, + STATE_DISABLED, + STATE_ENABLED + }) + public @interface ForensicState {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_UNKNOWN, + ERROR_PERMISSION_DENIED, + ERROR_TRANSPORT_UNAVAILABLE, + ERROR_DATA_SOURCE_UNAVAILABLE + }) + public @interface ForensicError {} + + /** + * Indicates an unknown state + */ + public static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; + + /** + * Indicates an state that the forensic is turned off. + */ + public static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED; + + /** + * Indicates an state that the forensic is turned on. + */ + public static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; + + /** + * Indicates an unknown error + */ + public static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; + + /** + * Indicates an error due to insufficient access rights. + */ + public static final int ERROR_PERMISSION_DENIED = + IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; + + /** + * Indicates an error due to unavailability of the forensic event transport. + */ + public static final int ERROR_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE; + + /** + * Indicates an error due to unavailability of the data source. + */ + public static final int ERROR_DATA_SOURCE_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; + + + private final IForensicService mService; + + private final ConcurrentHashMap, IForensicServiceStateCallback> + mStateCallbacks = new ConcurrentHashMap<>(); + + /** + * Constructor + * + * @param service A valid instance of IForensicService. + * @hide + */ + public ForensicManager(IForensicService service) { + mService = service; + } + + /** + * Add a callback to monitor the state of the ForensicService. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for state change. + * Once the callback is registered, the callback will be called + * to reflect the init state. + * The callback can be registered only once. + */ + @RequiresPermission(READ_FORENSIC_STATE) + public void addStateCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull @ForensicState Consumer callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + if (mStateCallbacks.get(callback) != null) { + Log.d(TAG, "addStateCallback callback already present"); + return; + } + + final IForensicServiceStateCallback wrappedCallback = + new IForensicServiceStateCallback.Stub() { + @Override + public void onStateChange(int state) { + executor.execute(() -> callback.accept(state)); + } + }; + try { + mService.addStateCallback(wrappedCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + mStateCallbacks.put(callback, wrappedCallback); + } + + /** + * Remove a callback to monitor the state of the ForensicService. + * + * @param callback The callback to remove. + */ + @RequiresPermission(READ_FORENSIC_STATE) + public void removeStateCallback(@NonNull Consumer<@ForensicState Integer> callback) { + Objects.requireNonNull(callback); + if (!mStateCallbacks.containsKey(callback)) { + Log.d(TAG, "removeStateCallback callback not present"); + return; + } + + IForensicServiceStateCallback wrappedCallback = mStateCallbacks.get(callback); + + try { + mService.removeStateCallback(wrappedCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + mStateCallbacks.remove(callback); + } + + /** + * Enable forensic logging. + * If successful, ForensicService will transition to {@link #STATE_ENABLED} state. + *

    + * When forensic logging is enabled, various device events will be collected and + * sent over to the registered {@link android.security.forensic.ForensicEventTransport}. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for the command result. + */ + @RequiresPermission(MANAGE_FORENSIC_STATE) + public void enable(@NonNull @CallbackExecutor Executor executor, + @NonNull CommandCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mService.enable(new IForensicServiceCommandCallback.Stub() { + @Override + public void onSuccess() { + executor.execute(callback::onSuccess); + } + + @Override + public void onFailure(int error) { + executor.execute(() -> callback.onFailure(error)); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Disable forensic logging. + * If successful, ForensicService will transition to {@link #STATE_DISABLED}. + *

    + * When forensic logging is disabled, device events will no longer be collected. + * Any events that have been collected but not yet sent to ForensicEventTransport + * will be transferred as a final batch. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for the command result. + */ + @RequiresPermission(MANAGE_FORENSIC_STATE) + public void disable(@NonNull @CallbackExecutor Executor executor, + @NonNull CommandCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mService.disable(new IForensicServiceCommandCallback.Stub() { + @Override + public void onSuccess() { + executor.execute(callback::onSuccess); + } + + @Override + public void onFailure(int error) { + executor.execute(() -> callback.onFailure(error)); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback used in {@link #enable} and {@link #disable} to indicate the result of the command. + */ + public interface CommandCallback { + /** + * Called when command succeeds. + */ + void onSuccess(); + + /** + * Called when command fails. + * @param error The error number. + */ + void onFailure(@ForensicError int error); + } +} diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IForensicEventTransport.aidl similarity index 96% rename from core/java/android/security/forensic/IBackupTransport.aidl rename to core/java/android/security/forensic/IForensicEventTransport.aidl index c2cbc83ba1b33bc6d552b22b9e5a52ec5299bb95..80e78eb9cf4948fae76a78ae1daf8ae0c98c8ef1 100644 --- a/core/java/android/security/forensic/IBackupTransport.aidl +++ b/core/java/android/security/forensic/IForensicEventTransport.aidl @@ -20,7 +20,7 @@ import android.security.forensic.ForensicEvent; import com.android.internal.infra.AndroidFuture; /** {@hide} */ -oneway interface IBackupTransport { +oneway interface IForensicEventTransport { /** * Initialize the server side. */ diff --git a/core/java/android/security/forensic/IForensicService.aidl b/core/java/android/security/forensic/IForensicService.aidl index a944b18cb26da4203925491c8eafec4e6d32024b..8039b264f0e5be7b9f943a2689243cc551328461 100644 --- a/core/java/android/security/forensic/IForensicService.aidl +++ b/core/java/android/security/forensic/IForensicService.aidl @@ -24,9 +24,12 @@ import android.security.forensic.IForensicServiceStateCallback; * @hide */ interface IForensicService { - void monitorState(IForensicServiceStateCallback callback); - void makeVisible(IForensicServiceCommandCallback callback); - void makeInvisible(IForensicServiceCommandCallback callback); + @EnforcePermission("READ_FORENSIC_STATE") + void addStateCallback(IForensicServiceStateCallback callback); + @EnforcePermission("READ_FORENSIC_STATE") + void removeStateCallback(IForensicServiceStateCallback callback); + @EnforcePermission("MANAGE_FORENSIC_STATE") void enable(IForensicServiceCommandCallback callback); + @EnforcePermission("MANAGE_FORENSIC_STATE") void disable(IForensicServiceCommandCallback callback); } diff --git a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl index 7fa0c7f726905f9dec5273a162709f98b0c7f613..6d1456ea04261d6d4fffd82f7811a17296e79529 100644 --- a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl +++ b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl @@ -25,8 +25,8 @@ package android.security.forensic; UNKNOWN = 0, PERMISSION_DENIED = 1, INVALID_STATE_TRANSITION = 2, - BACKUP_TRANSPORT_UNAVAILABLE = 3, - DATA_SOURCE_UNAVAILABLE = 3, + TRANSPORT_UNAVAILABLE = 3, + DATA_SOURCE_UNAVAILABLE = 4, } void onSuccess(); void onFailure(ErrorCode error); diff --git a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl index 0cda35083ffde9bd374474e6e34057c5c7a2dc8a..1b68c7b14bcac647a552cb0690f8cc9f8dabf973 100644 --- a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl +++ b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl @@ -23,9 +23,8 @@ package android.security.forensic; @Backing(type="int") enum State{ UNKNOWN = 0, - INVISIBLE = 1, - VISIBLE = 2, - ENABLED = 3, + DISABLED = 1, + ENABLED = 2, } void onStateChange(State state); } diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index 66e1f38621ae737eb658f3e0da69b24a6565199c..6c92991ceff6a4c8bca980c58ac7ac4a7077c4c6 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -103,3 +103,10 @@ flag { description: "Applies intentMatchingFlags while matching intents to application components" bug: "364354494" } + +flag { + name: "aapm_feature_disable_install_unknown_sources" + namespace: "responsible_apis" + description: "Android Advanced Protection Mode Feature: Disable Install Unknown Sources" + bug: "369361373" +} diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 98dda1031eff258b517dadb4ad24cd850f110fdd..14a14e69f208531f91ff44ff4d07f4b01fc68c76 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -16,8 +16,10 @@ package android.service.autofill; +import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS; import static android.view.autofill.Helper.sVerbose; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -63,14 +65,21 @@ public final class FillEventHistory implements Parcelable { private static final String TAG = "FillEventHistory"; /** - * Not in parcel. The ID of the autofill session that created the {@link FillResponse}. + * The ID of the autofill session that created the {@link FillResponse}. + * + * TODO: add this to the parcel. */ private final int mSessionId; @Nullable private final Bundle mClientState; @Nullable List mEvents; - /** @hide */ + /** + * Returns the unique identifier of this FillEventHistory. + * + *

    This is used to differentiate individual FillEventHistory. + */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) public int getSessionId() { return mSessionId; } @@ -283,6 +292,13 @@ public final class FillEventHistory implements Parcelable { /** All fields matched contents of datasets. */ public static final int NO_SAVE_UI_REASON_DATASET_MATCH = 6; + /** + * Credential Manager is invoked instead of Autofill. When that happens, Save Dialog cannot + * be shown, and this will be populated in + */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + public static final int NO_SAVE_UI_REASON_USING_CREDMAN = 7; + /** @hide */ @IntDef(prefix = { "NO_SAVE_UI_REASON_" }, value = { NO_SAVE_UI_REASON_NONE, @@ -310,11 +326,20 @@ public final class FillEventHistory implements Parcelable { public static final int UI_TYPE_DIALOG = 3; /** - * The autofill suggestion is shown os a credman bottom sheet - * @hide + * The autofill suggestion is shown os a credman bottom sheet + * + *

    Note, this was introduced as bottom sheet even though it applies to all credman UI + * types. Instead of exposing this directly to the public, the generic UI_TYPE_CREDMAN is + * introduced with the same number. + * + * @hide */ public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4; + /** Credential Manager suggestions are shown instead of Autofill suggestion */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + public static final int UI_TYPE_CREDMAN = 4; + /** @hide */ @IntDef(prefix = { "UI_TYPE_" }, value = { UI_TYPE_UNKNOWN, @@ -359,6 +384,13 @@ public final class FillEventHistory implements Parcelable { return mEventType; } + /** Gets the {@code AutofillId} that's focused at the time of action */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + @Nullable + public AutofillId getFocusedId() { + return null; + } + /** * Returns the id of dataset the id was on. * @@ -390,6 +422,17 @@ public final class FillEventHistory implements Parcelable { : new ArraySet<>(mSelectedDatasetIds); } + /** + * Returns which datasets were shown to the user. + * + *

    Note: Only set on events of type {@link #TYPE_DATASETS_SHOWN}. + */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + @NonNull + public Set getShownDatasetIds() { + return Collections.emptySet(); + } + /** * Returns which datasets were NOT selected by the user. * diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index ca20801852f1b73fec58c172716ed570c407edf6..be4629ab8178370b1254a46ec06313cf4620ba65 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -16,6 +16,9 @@ package android.service.autofill; +import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -100,7 +103,12 @@ public final class FillRequest implements Parcelable { /** * Indicates the request supports fill dialog presentation for the fields, the * system will send the request when the activity just started. + * + * @deprecated All requests would support fill dialog by default. + * Presence of this flag isn't needed. */ + @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS) + @Deprecated public static final @RequestFlags int FLAG_SUPPORTS_FILL_DIALOG = 0x40; /** @@ -588,10 +596,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1701010178309L, + time = 1730991738865L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java", - inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List mFillContexts\nprivate final @android.annotation.NonNull java.util.List mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.annotation.FlaggedApi @java.lang.Deprecated @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PCC_DETECTION\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SCREEN_HAS_CREDMAN_FIELD\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_REQUESTS_CREDMAN_SERVICE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List mFillContexts\nprivate final @android.annotation.NonNull java.util.List mHints\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index b384b66bf6807c3a29b13429f97e18573b6ec9cb..54710488cd6735cd0db5e5023ad50ee7f32404e9 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -34,10 +34,14 @@ oneway interface INotificationListener void onListenerConnected(in NotificationRankingUpdate update); void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder, in NotificationRankingUpdate update); + void onNotificationPostedFull(in StatusBarNotification sbn, + in NotificationRankingUpdate update); void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons); // stats only for assistant void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder, in NotificationRankingUpdate update, in NotificationStats stats, int reason); + void onNotificationRemovedFull(in StatusBarNotification sbn, + in NotificationRankingUpdate update, in NotificationStats stats, int reason); void onNotificationRankingUpdate(in NotificationRankingUpdate update); void onListenerHintsChanged(int hints); void onInterruptionFilterChanged(int interruptionFilter); @@ -48,7 +52,9 @@ oneway interface INotificationListener // assistants only void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update); + void onNotificationEnqueuedWithChannelFull(in StatusBarNotification sbn, in NotificationChannel channel, in NotificationRankingUpdate update); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); + void onNotificationSnoozedUntilContextFull(in StatusBarNotification sbn, String snoozeCriterionId); void onNotificationsSeen(in List keys); void onPanelRevealed(int items); void onPanelHidden(); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 091b25ab77ceb0663f1520265ae091b86c98c678..0a9276c34bfd1aa94cf45db9666749509a00cece 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -423,7 +423,12 @@ public abstract class NotificationAssistantService extends NotificationListenerS + "Error receiving StatusBarNotification"); return; } + onNotificationEnqueuedWithChannelFull(sbn, channel, update); + } + @Override + public void onNotificationEnqueuedWithChannelFull(StatusBarNotification sbn, + NotificationChannel channel, NotificationRankingUpdate update) { applyUpdateLocked(update); SomeArgs args = SomeArgs.obtain(); args.arg1 = sbn; @@ -447,7 +452,12 @@ public abstract class NotificationAssistantService extends NotificationListenerS Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification"); return; } + onNotificationSnoozedUntilContextFull(sbn, snoozeCriterionId); + } + @Override + public void onNotificationSnoozedUntilContextFull( + StatusBarNotification sbn, String snoozeCriterionId) { SomeArgs args = SomeArgs.obtain(); args.arg1 = sbn; args.arg2 = snoozeCriterionId; diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index a8ab2115d97f7e1d216c2a38de153dbacafaa5c1..5d0ec73a024bb9da12c8bc8d283f8da53fa5ef55 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1490,7 +1490,12 @@ public abstract class NotificationListenerService extends Service { Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification"); return; } + onNotificationPostedFull(sbn, update); + } + @Override + public void onNotificationPostedFull(StatusBarNotification sbn, + NotificationRankingUpdate update) { try { // convert icon metadata to legacy format for older clients createLegacyIconExtras(sbn.getNotification()); @@ -1518,7 +1523,6 @@ public abstract class NotificationListenerService extends Service { mRankingMap).sendToTarget(); } } - } @Override @@ -1531,6 +1535,12 @@ public abstract class NotificationListenerService extends Service { Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e); return; } + onNotificationRemovedFull(sbn, update, stats, reason); + } + + @Override + public void onNotificationRemovedFull(StatusBarNotification sbn, + NotificationRankingUpdate update, NotificationStats stats, int reason) { if (sbn == null) { Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification"); return; @@ -1591,6 +1601,14 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } + @Override + public void onNotificationEnqueuedWithChannelFull( + StatusBarNotification sbn, NotificationChannel channel, + NotificationRankingUpdate update) + throws RemoteException { + // no-op in the listener + } + @Override public void onNotificationsSeen(List keys) throws RemoteException { @@ -1620,6 +1638,13 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } + @Override + public void onNotificationSnoozedUntilContextFull( + StatusBarNotification sbn, String snoozeCriterionId) + throws RemoteException { + // no-op in the listener + } + @Override public void onNotificationExpansionChanged( String key, boolean isUserAction, boolean isExpanded) { @@ -1688,8 +1713,6 @@ public abstract class NotificationListenerService extends Service { Bundle feedback) { // no-op in the listener } - - } /** diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl index 0dca78d890a5109bf9f5698272b4bfba468aa155..03e27b866b7a4a01a514bebafac85da0b58aa1bc 100644 --- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl +++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl @@ -43,4 +43,6 @@ interface IQuickAccessWalletService { oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request); // Request to get a PendingIntent to launch an activity from which the user can manage their cards. oneway void onTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks); + // Request to get a PendingIntent to launch an activity, triggered when the user performs a gesture. + oneway void onGestureTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks); } \ No newline at end of file diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl index 1b69ca12da3a19b1f8293944ac7a7a0cf7e2936a..61d7fd1f526c36a967826625d52345dbd5ff61ac 100644 --- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl +++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl @@ -37,4 +37,6 @@ interface IQuickAccessWalletServiceCallbacks { oneway void onWalletServiceEvent(in WalletServiceEvent event); // Called in response to onTargetActivityIntentRequested. May only be called once per request. oneway void onTargetActivityPendingIntentReceived(in PendingIntent pendingIntent); + //Called in response to onGesturePendingIntent + oneway void onGestureTargetActivityPendingIntentReceived(in PendingIntent pendingIntent); } \ No newline at end of file diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java index faa5b2fe3488508511c45df8aaf41c1508bce72c..b5251db4e5397be1def83e35ff06ea57ddb998a6 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java @@ -17,6 +17,7 @@ package android.service.quickaccesswallet; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -180,6 +181,23 @@ public interface QuickAccessWalletClient extends Closeable { void onWalletPendingIntentRetrieved(@Nullable PendingIntent walletPendingIntent); } + /** + * Gets the {@link PendingIntent} provided by QuickAccessWalletService to be sent when the user + * launches Wallet via gesture. + */ + @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + void getGestureTargetActivityPendingIntent( + @NonNull @CallbackExecutor Executor executor, + @NonNull GesturePendingIntentCallback gesturePendingIntentCallback); + + /** Callback interface for getGesturePendingIntent. */ + @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + interface GesturePendingIntentCallback { + /** Callback method for getGesturePendingIntent */ + @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + void onGesturePendingIntentRetrieved(@Nullable PendingIntent pendingIntent); + } + /** * The manifest entry for the QuickAccessWalletService may also publish information about the * activity that hosts the Wallet view. This is typically the home screen of the Wallet diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index a59f026c41820eb4d1febe2eef174c7f20a934bc..97a4beff633fdf8e832cfc7355e386aad2fcb620 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -266,6 +266,34 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser }); } + @Override + public void getGestureTargetActivityPendingIntent( + @NonNull @CallbackExecutor Executor executor, + @NonNull GesturePendingIntentCallback gesturePendingIntentCallback) { + BaseCallbacks callbacks = + new BaseCallbacks() { + @Override + public void onGestureTargetActivityPendingIntentReceived( + PendingIntent pendingIntent) { + if (!Flags.launchWalletOptionOnPowerDoubleTap()) { + return; + } + executor.execute( + () -> + gesturePendingIntentCallback + .onGesturePendingIntentRetrieved(pendingIntent)); + } + }; + + executeApiCall( + new ApiCaller("getGestureTargetActivityPendingIntent") { + @Override + void performApiCall(IQuickAccessWalletService service) throws RemoteException { + service.onGestureTargetActivityIntentRequested(callbacks); + } + }); + } + @Override @Nullable public Intent createWalletSettingsIntent() { @@ -506,5 +534,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) { throw new IllegalStateException(); } + + public void onGestureTargetActivityPendingIntentReceived(PendingIntent pendingIntent) { + throw new IllegalStateException(); + } } } diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java index 36fa21c19d273f45ac3c448f5779a273a2e5caf6..90136ae00f6a87ca3b0f8b3f347ae96f92242ff6 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java @@ -16,6 +16,9 @@ package android.service.quickaccesswallet; +import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -247,6 +250,19 @@ public abstract class QuickAccessWalletService extends Service { callbacks)); } + @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + @Override + public void onGestureTargetActivityIntentRequested( + @NonNull IQuickAccessWalletServiceCallbacks callbacks) { + if (launchWalletOptionOnPowerDoubleTap()) { + mHandler.post( + () -> + QuickAccessWalletService.this + .onGestureTargetActivityIntentRequestedInternal( + callbacks)); + } + } + public void registerWalletServiceEventListener( @NonNull WalletServiceEventListenerRequest request, @NonNull IQuickAccessWalletServiceCallbacks callback) { @@ -275,6 +291,20 @@ public abstract class QuickAccessWalletService extends Service { } } + private void onGestureTargetActivityIntentRequestedInternal( + IQuickAccessWalletServiceCallbacks callbacks) { + if (!Flags.launchWalletOptionOnPowerDoubleTap()) { + return; + } + + try { + callbacks.onGestureTargetActivityPendingIntentReceived( + getGestureTargetActivityPendingIntent()); + } catch (RemoteException e) { + Log.w(TAG, "Error", e); + } + } + @Override @Nullable public IBinder onBind(@NonNull Intent intent) { @@ -349,6 +379,18 @@ public abstract class QuickAccessWalletService extends Service { return null; } + /** + * Specify a {@link PendingIntent} to be launched on user gesture. + * + *

    The pending intent will be sent when the user performs a gesture to open Wallet. + * The pending intent should launch an activity. + */ + @Nullable + @FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public PendingIntent getGestureTargetActivityPendingIntent() { + return null; + } + private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) { if (mEventListener == null) { Log.i(TAG, "No dismiss listener registered"); diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..75a93091eec3433afbbb5b6d3984f9fda039650f --- /dev/null +++ b/core/java/android/service/quickaccesswallet/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.quickaccesswallet" +container: "system" + +flag { + name: "launch_wallet_option_on_power_double_tap" + namespace: "wallet_integration" + description: "Option to launch the Wallet app on double-tap of the power button" + bug: "378469025" +} \ No newline at end of file diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 1df3b433275488ca2d536f79cf8a70b57d3cacfa..c16a510ed7297f8b16810a8b620cb61a3c86858d 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -1712,6 +1712,15 @@ public class PhoneStateListener { @NonNull NtnSignalStrength ntnSignalStrength) { // not supported on the deprecated interface - Use TelephonyCallback instead } + + public final void onSecurityAlgorithmsChanged(SecurityAlgorithmUpdate update) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } + + public final void onCellularIdentifierDisclosedChanged( + CellularIdentifierDisclosure disclosure) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } } private void log(String s) { diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java index 7b48a16c2227be881a8b8d3b9de65f2419479e20..4c59a8589df228e826ee142c3bbd9624418bb8ff 100644 --- a/core/java/android/telephony/SubscriptionPlan.java +++ b/core/java/android/telephony/SubscriptionPlan.java @@ -18,6 +18,7 @@ package android.telephony; import android.annotation.BytesLong; import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.telephony.Annotation.NetworkType; import android.util.Range; import android.util.RecurrenceRule; +import com.android.internal.telephony.flags.Flags; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -83,6 +85,33 @@ public final class SubscriptionPlan implements Parcelable { /** Value indicating a timestamp is unknown. */ public static final long TIME_UNKNOWN = -1; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "SUBSCRIPTION_STATUS_" }, value = { + SUBSCRIPTION_STATUS_UNKNOWN, + SUBSCRIPTION_STATUS_ACTIVE, + SUBSCRIPTION_STATUS_INACTIVE, + SUBSCRIPTION_STATUS_TRIAL, + SUBSCRIPTION_STATUS_SUSPENDED + }) + public @interface SubscriptionStatus {} + + /** Subscription status is unknown. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0; + /** Subscription is active. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public static final int SUBSCRIPTION_STATUS_ACTIVE = 1; + /** Subscription is inactive. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public static final int SUBSCRIPTION_STATUS_INACTIVE = 2; + /** Subscription is in a trial period. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public static final int SUBSCRIPTION_STATUS_TRIAL = 3; + /** Subscription is suspended. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4; + private final RecurrenceRule cycleRule; private CharSequence title; private CharSequence summary; @@ -91,6 +120,7 @@ public final class SubscriptionPlan implements Parcelable { private long dataUsageBytes = BYTES_UNKNOWN; private long dataUsageTime = TIME_UNKNOWN; private @NetworkType int[] networkTypes; + private int mSubscriptionStatus = SUBSCRIPTION_STATUS_UNKNOWN; private SubscriptionPlan(RecurrenceRule cycleRule) { this.cycleRule = Preconditions.checkNotNull(cycleRule); @@ -107,6 +137,7 @@ public final class SubscriptionPlan implements Parcelable { dataUsageBytes = source.readLong(); dataUsageTime = source.readLong(); networkTypes = source.createIntArray(); + mSubscriptionStatus = source.readInt(); } @Override @@ -124,6 +155,7 @@ public final class SubscriptionPlan implements Parcelable { dest.writeLong(dataUsageBytes); dest.writeLong(dataUsageTime); dest.writeIntArray(networkTypes); + dest.writeInt(mSubscriptionStatus); } @Override @@ -137,13 +169,14 @@ public final class SubscriptionPlan implements Parcelable { .append(" dataUsageBytes=").append(dataUsageBytes) .append(" dataUsageTime=").append(dataUsageTime) .append(" networkTypes=").append(Arrays.toString(networkTypes)) + .append(" subscriptionStatus=").append(mSubscriptionStatus) .append("}").toString(); } @Override public int hashCode() { return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior, - dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes)); + dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes), mSubscriptionStatus); } @Override @@ -157,7 +190,8 @@ public final class SubscriptionPlan implements Parcelable { && dataLimitBehavior == other.dataLimitBehavior && dataUsageBytes == other.dataUsageBytes && dataUsageTime == other.dataUsageTime - && Arrays.equals(networkTypes, other.networkTypes); + && Arrays.equals(networkTypes, other.networkTypes) + && mSubscriptionStatus == other.mSubscriptionStatus; } return false; } @@ -179,6 +213,13 @@ public final class SubscriptionPlan implements Parcelable { return cycleRule; } + /** Return the end date of this plan, or null if no end date exists. */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public @Nullable ZonedDateTime getPlanEndDate() { + // ZonedDateTime is immutable, so no need to create a defensive copy. + return cycleRule.end; + } + /** Return the short title of this plan. */ public @Nullable CharSequence getTitle() { return title; @@ -237,6 +278,16 @@ public final class SubscriptionPlan implements Parcelable { return cycleRule.cycleIterator(); } + /** + * Returns the status of the subscription plan. + * + * @return The subscription status, or {@link #SUBSCRIPTION_STATUS_UNKNOWN} if not available. + */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public @SubscriptionStatus int getSubscriptionStatus() { + return mSubscriptionStatus; + } + /** * Builder for a {@link SubscriptionPlan}. */ @@ -382,5 +433,21 @@ public final class SubscriptionPlan implements Parcelable { TelephonyManager.getAllNetworkTypes().length); return this; } + + /** + * Set the subscription status. + * + * @param subscriptionStatus the current subscription status + */ + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + public @NonNull Builder setSubscriptionStatus(@SubscriptionStatus int subscriptionStatus) { + if (subscriptionStatus < SUBSCRIPTION_STATUS_UNKNOWN + || subscriptionStatus > SUBSCRIPTION_STATUS_SUSPENDED) { + throw new IllegalArgumentException( + "Subscription status must be defined with a valid value"); + } + plan.mSubscriptionStatus = subscriptionStatus; + return this; + } } } diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 0d1dc4611343a409a9b48dcda221514c10ba14cf..2c585e640fdd19ddc091644aa61aca8f36018981 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -704,6 +704,28 @@ public class TelephonyCallback { */ public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45; + /** + * Event for changes to mobile network ciphering algorithms. + * See {@link SecurityAlgorithmsListener#onSecurityAlgorithmsChanged} + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public static final int EVENT_SECURITY_ALGORITHMS_CHANGED = 46; + + /** + * Event for updates to sensitive device identifier disclosures (IMSI, IMEI, unciphered SUCI). + * See {@link CellularIdentifierDisclosedListener#onCellularIdentifierDisclosedChanged} + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; + /** * @hide */ @@ -752,7 +774,9 @@ public class TelephonyCallback { EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED, EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED, EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED, - EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED + EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED, + EVENT_SECURITY_ALGORITHMS_CHANGED, + EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface TelephonyEvent { @@ -1826,6 +1850,41 @@ public class TelephonyCallback { @NonNull NtnSignalStrength ntnSignalStrength) {} } + /** + * Interface for CellularIdentifierDisclosedListener + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS) + public interface CellularIdentifierDisclosedListener { + /** + * Callback invoked when a device identifier (IMSI, IMEI, or unciphered SUCI) + * is disclosed over the network before a security context is established + * ("pre-authentication"). + * + * @param disclosure details of the identifier disclosure + * See {@link CellularIdentifierDisclosure} for more details + */ + void onCellularIdentifierDisclosedChanged(@NonNull CellularIdentifierDisclosure disclosure); + } + + /** + * Interface for SecurityAlgorithmsListener + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_SECURITY_ALGORITHMS_UPDATE_INDICATIONS) + public interface SecurityAlgorithmsListener { + /** + * Callback invoked when the most recently reported security algorithms has changed, + * per a specified connection event. + * + * @param securityAlgorithmUpdate details of the security algorithm update + * See {@link SecurityAlgorithmUpdate} for more details + */ + void onSecurityAlgorithmsChanged(@NonNull SecurityAlgorithmUpdate securityAlgorithmUpdate); + } + /** * The callback methods need to be called on the handler thread where * this object was created. If the binder did that for us it'd be nice. @@ -2302,5 +2361,27 @@ public class TelephonyCallback { () -> listener.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength))); } + + public void onSecurityAlgorithmsChanged(SecurityAlgorithmUpdate update) { + if (!Flags.securityAlgorithmsUpdateIndications()) return; + + SecurityAlgorithmsListener listener = + (SecurityAlgorithmsListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity(() -> mExecutor.execute( + () -> listener.onSecurityAlgorithmsChanged(update))); + } + + public void onCellularIdentifierDisclosedChanged(CellularIdentifierDisclosure disclosure) { + if (!Flags.cellularIdentifierDisclosureIndications()) return; + + CellularIdentifierDisclosedListener listener = + (CellularIdentifierDisclosedListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity(() -> mExecutor.execute( + () -> listener.onCellularIdentifierDisclosedChanged(disclosure))); + } } } diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 90b0bb34c145e37b95ee3d4c6a659c5fac5eb4a9..4ec429d0c4adae1eecc9a6a12e1acc9131e5e99e 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -1154,6 +1154,40 @@ public class TelephonyRegistryManager { } } + /** + * Notify external listeners that the radio security algorithms have changed. + * @param slotIndex for the phone object that got updated + * @param subId for which the security algorithm changed + * @param update details of the security algorithm update + * @hide + */ + public void notifySecurityAlgorithmsChanged( + int slotIndex, int subId, SecurityAlgorithmUpdate update) { + try { + sRegistry.notifySecurityAlgorithmsChanged(slotIndex, subId, update); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Notify external listeners of a new cellular identifier disclosure change. + * @param slotIndex for the phone object that the disclosure applies to + * @param subId for which the disclosure applies to + * @param disclosure details of the identifier disclosure + * @hide + */ + public void notifyCellularIdentifierDisclosedChanged( + int slotIndex, int subId, CellularIdentifierDisclosure disclosure) { + try { + sRegistry.notifyCellularIdentifierDisclosedChanged(slotIndex, subId, disclosure); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + /** * Processes potential event changes from the provided {@link TelephonyCallback}. * @@ -1313,6 +1347,15 @@ public class TelephonyRegistryManager { eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED); eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED); } + + if (telephonyCallback instanceof TelephonyCallback.CellularIdentifierDisclosedListener) { + eventList.add(TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED); + } + + if (telephonyCallback instanceof TelephonyCallback.SecurityAlgorithmsListener) { + eventList.add(TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED); + } + return eventList; } diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index ae12132d49a1f55a8ae1213c8d8811e0c3e97d9c..a42eece57eec99c164876a4080962e38ccdda952 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -614,7 +614,7 @@ public class Html { if (style[j] instanceof TypefaceSpan) { String s = ((TypefaceSpan) style[j]).getFamily(); - if (s.equals("monospace")) { + if ("monospace".equals(s)) { out.append(""); } } diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java deleted file mode 100644 index f69a333ff81f5100c2c69f6bdbc3025278ccd6dc..0000000000000000000000000000000000000000 --- a/core/java/android/text/TextFlags.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.text; - -import android.annotation.NonNull; -import android.app.AppGlobals; - -/** - * Flags in the "text" namespace. - * - * TODO(nona): Remove this class. - * @hide - */ -public final class TextFlags { - - /** - * The name space of the "text" feature. - * - * This needs to move to DeviceConfig constant. - */ - public static final String NAMESPACE = "text"; - - /** - * Whether we use the new design of context menu. - */ - public static final String ENABLE_NEW_CONTEXT_MENU = - "TextEditing__enable_new_context_menu"; - - /** - * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}. - */ - public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu"; - - /** - * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}. - */ - public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = true; - - /** - * List of text flags to be transferred to the application process. - */ - public static final String[] TEXT_ACONFIGS_FLAGS = { - }; - - /** - * List of the default values of the text flags. - * - * The order must be the same to the TEXT_ACONFIG_FLAGS. - */ - public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { - }; - - /** - * Get a key for the feature flag. - */ - public static String getKeyForFlag(@NonNull String flag) { - return "text__" + flag; - } - - /** - * Return true if the feature flag is enabled. - */ - public static boolean isFeatureEnabled(@NonNull String flag) { - return AppGlobals.getIntCoreSetting( - getKeyForFlag(flag), 0 /* aconfig is false by default */) != 0; - } -} diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 032f5923d3f2cae0e553d4b5d9ea5fd125950c0e..cb72b976c784778f4cff957ea63c0357f4321ef5 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -50,6 +50,7 @@ import android.text.style.LineBackgroundSpan; import android.text.style.LineBreakConfigSpan; import android.text.style.LineHeightSpan; import android.text.style.LocaleSpan; +import android.text.style.NoWritingToolsSpan; import android.text.style.ParagraphStyle; import android.text.style.QuoteSpan; import android.text.style.RelativeSizeSpan; @@ -817,7 +818,9 @@ public class TextUtils { /** @hide */ public static final int LINE_BREAK_CONFIG_SPAN = 30; /** @hide */ - public static final int LAST_SPAN = LINE_BREAK_CONFIG_SPAN; + public static final int NO_WRITING_TOOLS_SPAN = 31; + /** @hide */ + public static final int LAST_SPAN = NO_WRITING_TOOLS_SPAN; /** * Flatten a CharSequence and whatever styles can be copied across processes @@ -1025,6 +1028,10 @@ public class TextUtils { span = LineBreakConfigSpan.CREATOR.createFromParcel(p); break; + case NO_WRITING_TOOLS_SPAN: + span = NoWritingToolsSpan.CREATOR.createFromParcel(p); + break; + default: throw new RuntimeException("bogus span encoding " + kind); } diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 02923eda308ef5d3a90ab56e3f2f28c8a331becc..f43f172d7d5b622b6bf20eef30d4a29b108df707 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -163,10 +163,12 @@ flag { } flag { - name: "typeface_redesign" + name: "typeface_redesign_readonly" namespace: "text" description: "Decouple variation settings, weight and style information from Typeface class" bug: "361260253" + # This feature does not support runtime flag switch which leads crash in System UI. + is_fixed_read_only: true } flag { diff --git a/core/java/android/text/style/NoWritingToolsSpan.java b/core/java/android/text/style/NoWritingToolsSpan.java new file mode 100644 index 0000000000000000000000000000000000000000..90f85aa69faa5df31c6f00d5121c918384c34b48 --- /dev/null +++ b/core/java/android/text/style/NoWritingToolsSpan.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.text.style; + +import static android.view.inputmethod.Flags.FLAG_WRITING_TOOLS; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.text.ParcelableSpan; +import android.text.TextUtils; + +/** + * A span that signals to IMEs that writing tools should not modify the text. + * + *

    For example, a text field may contain a mix of user input text and quoted text. The app + * can apply {@code NoWritingToolsSpan} to the quoted text so that the IME knows that writing + * tools should only rewrite the user input text, and not modify the quoted text. + */ +@FlaggedApi(FLAG_WRITING_TOOLS) +public final class NoWritingToolsSpan implements ParcelableSpan { + + /** + * Creates a {@link NoWritingToolsSpan}. + */ + public NoWritingToolsSpan() { + } + + @Override + public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** @hide */ + @Override + public int getSpanTypeIdInternal() { + return TextUtils.NO_WRITING_TOOLS_SPAN; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** @hide */ + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { + } + + @Override + public String toString() { + return "NoWritingToolsSpan{}"; + } + + @NonNull + public static final Creator CREATOR = new Creator<>() { + + @Override + public NoWritingToolsSpan createFromParcel(Parcel source) { + return new NoWritingToolsSpan(); + } + + @Override + public NoWritingToolsSpan[] newArray(int size) { + return new NoWritingToolsSpan[size]; + } + }; +} diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index 174e0c8e6549e55b176f1d4953fb7f96ab879951..7ee0ff15c5ad2e903e4afe6010d2d9355847bcf9 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -649,7 +649,6 @@ public final class ArrayMap implements Map { } if (index > 0 && mHashes[index-1] > hash) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "New hash " + hash + " is before end of array hash " + mHashes[index-1] + " at index " + index + (DEBUG ? " key " + key : ""), e); diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java index bfbca07ed2568ce0055a6159c7f76d2389a3403b..1344bb9a73eb56feff1c209645c85155f4399972 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -526,7 +526,6 @@ public final class ArraySet implements Collection, Set { // Cannot optimize since it would break the sorted order - fallback to add() if (DEBUG) { RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); Log.w(TAG, "New hash " + hash + " is before end of array hash " + mHashes[index - 1] + " at index " + index, e); diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index 5406cf5574106f59fa922fd0853b07595689a046..264db4a604ffd9d8138f8e9e78d4597c243ad54e 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -15,9 +15,11 @@ */ package android.view; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UiThread; import android.content.Context; import android.graphics.Rect; @@ -29,6 +31,8 @@ import android.window.SurfaceSyncGroup; import com.android.window.flags.Flags; +import java.util.concurrent.Executor; + /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This * is used in combination with the {@link android.view.SurfaceControl} API to enable @@ -202,4 +206,21 @@ public interface AttachedSurfaceControl { throw new UnsupportedOperationException("The getInputTransferToken needs to be " + "implemented before making this call."); } + + /** + * Registers a {@link OnJankDataListener} to receive jank classification data about rendered + * frames. + * + * @param executor The executor on which the listener will be invoked. + * @param listener The listener to add. + * @return The {@link OnJankDataListenerRegistration} for the listener. + */ + @NonNull + @FlaggedApi(Flags.FLAG_JANK_API) + @SuppressLint("PairedRegistration") + default SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull SurfaceControl.OnJankDataListener listener) { + return SurfaceControl.OnJankDataListenerRegistration.NONE; + } } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 7e247493e35ca10e90958d0d819cb3adf7a099dd..8cb96ae1d611258f550491bd43de75f89ee6b6d6 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -189,6 +189,11 @@ public final class Choreographer { @UnsupportedAppUsage private long mLastFrameTimeNanos; + // Keeps track of the last scheduled frame time without additional offsets + // added from buffer stuffing recovery. Used to compare timing of vsyncs to + // determine idle state. + private long mLastNoOffsetFrameTimeNanos; + /** DO NOT USE since this will not updated when screen refresh changes. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead") @@ -203,6 +208,50 @@ public final class Choreographer { private final FrameData mFrameData = new FrameData(); private volatile boolean mInDoFrameCallback = false; + private static class BufferStuffingData { + enum RecoveryAction { + // No recovery + NONE, + // Recovery has started, adds a negative offset + OFFSET, + // Recovery has started, delays a frame to return buffer count + // back toward threshold. + DELAY_FRAME + } + // The maximum number of times frames will be delayed per buffer stuffing event. + // Since buffer stuffing can persist for several consecutive frames following the + // initial missed frame, we want to adjust the timeline with enough frame delays and + // offsets to return the queued buffer count back to threshold. + public static final int MAX_FRAME_DELAYS = 3; + + // Whether buffer stuffing recovery has begun. Recovery can only end + // when events are idle. + public boolean isRecovering = false; + + // The number of frames delayed so far during recovery. Used to compare with + // MAX_FRAME_DELAYS to safeguard against excessive frame delays during recovery. + // Also used as unique cookie for tracing. + public int numberFrameDelays = 0; + + // The number of additional frame delays scheduled during recovery to wait for the next + // vsync. These are scheduled when frame times appear to go backward or frames are + // being skipped due to FPSDivisor. + public int numberWaitsForNextVsync = 0; + + /** + * After buffer stuffing recovery has ended with a detected idle state, the + * recovery data trackers can be reset in preparation for any future + * stuffing events. + */ + public void reset() { + isRecovering = false; + numberFrameDelays = 0; + numberWaitsForNextVsync = 0; + } + } + + private final BufferStuffingData mBufferStuffingData = new BufferStuffingData(); + /** * Contains information about the current frame for jank-tracking, * mainly timings of key events along with a bit of metadata about @@ -850,13 +899,99 @@ public final class Choreographer { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + // Conducts logic for beginning or ending buffer stuffing recovery. + // Returns an enum for the recovery action that should be taken in doFrame(). + BufferStuffingData.RecoveryAction checkBufferStuffingRecovery(long frameTimeNanos, + DisplayEventReceiver.VsyncEventData vsyncEventData) { + // Canned animations can recover from buffer stuffing whenever more + // than 2 buffers are queued. + if (vsyncEventData.numberQueuedBuffers > 2) { + mBufferStuffingData.isRecovering = true; + // Intentional frame delay that can happen at most MAX_FRAME_DELAYS times per + // buffer stuffing event until the buffer count returns to threshold. The + // delayed frames are compensated for by the negative offsets added to the + // animation timestamps. + if (mBufferStuffingData.numberFrameDelays < mBufferStuffingData.MAX_FRAME_DELAYS) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.asyncTraceForTrackBegin( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread " + + android.os.Process.myTid() + ", recover frame #" + + mBufferStuffingData.numberFrameDelays, + mBufferStuffingData.numberFrameDelays); + } + mBufferStuffingData.numberFrameDelays++; + scheduleVsyncLocked(); + return BufferStuffingData.RecoveryAction.DELAY_FRAME; + } + } + + if (mBufferStuffingData.isRecovering) { + // Includes an additional expected frame delay from the natural scheduling + // of the next vsync event. + int totalFrameDelays = mBufferStuffingData.numberFrameDelays + + mBufferStuffingData.numberWaitsForNextVsync + 1; + long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0 + ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0; + + // Detected idle state due to a longer inactive period since the last vsync callback + // than the total expected number of vsync frame delays. End buffer stuffing recovery. + // There are no frames to animate and offsets no longer need to be added + // since the idle state gives the animation a chance to catch up. + if (vsyncsSinceLastCallback > totalFrameDelays) { + if (DEBUG_JANK) { + Log.d(TAG, "End buffer stuffing recovery"); + } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + for (int i = 0; i < mBufferStuffingData.numberFrameDelays; i++) { + Trace.asyncTraceForTrackEnd( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", i); + } + } + mBufferStuffingData.reset(); + + } else { + if (DEBUG_JANK) { + Log.d(TAG, "Adjust animation timeline with a negative offset"); + } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instantForTrack( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", + "Negative offset added to animation"); + } + return BufferStuffingData.RecoveryAction.OFFSET; + } + } + return BufferStuffingData.RecoveryAction.NONE; + } + void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; boolean resynced = false; + long offsetFrameTimeNanos = frameTimeNanos; + + // Evaluate if buffer stuffing recovery needs to start or end, and + // what actions need to be taken for recovery. + switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) { + case NONE: + // Without buffer stuffing recovery, offsetFrameTimeNanos is + // synonymous with frameTimeNanos. + break; + case OFFSET: + // Add animation offset. Used to update frame timeline with + // offset before jitter is calculated. + offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; + break; + case DELAY_FRAME: + // Intentional frame delay to help restore queued buffer count to threshold. + return; + default: + break; + } + try { - FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData); + FrameTimeline timeline = mFrameData.update(offsetFrameTimeNanos, vsyncEventData); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId); @@ -867,15 +1002,18 @@ public final class Choreographer { traceMessage("Frame not scheduled"); return; // no work to do } + mLastNoOffsetFrameTimeNanos = frameTimeNanos; if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { mDebugPrintNextFrameTimeDelta = false; Log.d(TAG, "Frame time delta: " - + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); + + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } - long intendedFrameTimeNanos = frameTimeNanos; + long intendedFrameTimeNanos = offsetFrameTimeNanos; startNanos = System.nanoTime(); + // Calculating jitter involves using the original frame time without + // adjustments from buffer stuffing final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= frameIntervalNanos) { frameTimeNanos = startNanos; @@ -899,6 +1037,13 @@ public final class Choreographer { + " ms in the past."); } } + if (mBufferStuffingData.isRecovering) { + frameTimeNanos -= frameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Adjusted animation timeline with a negative offset after" + + " jitter calculation"); + } + } timeline = mFrameData.update( frameTimeNanos, mDisplayEventReceiver, jitterNanos); resynced = true; @@ -910,6 +1055,9 @@ public final class Choreographer { + "previously skipped frame. Waiting for next vsync."); } traceMessage("Frame time goes backward"); + if (mBufferStuffingData.isRecovering) { + mBufferStuffingData.numberWaitsForNextVsync++; + } scheduleVsyncLocked(); return; } @@ -918,6 +1066,9 @@ public final class Choreographer { long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { traceMessage("Frame skipped due to FPSDivisor"); + if (mBufferStuffingData.isRecovering) { + mBufferStuffingData.numberWaitsForNextVsync++; + } scheduleVsyncLocked(); return; } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0241e943795077e5b9f0d914b7567e8e54096aa5..a1a9fc697271cf2411df978e7ea8af4cd38bc54e 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1786,7 +1786,12 @@ public final class Display { * {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the * returned metrics provide the size of the current app window. As a result, in * multi-window mode, the returned size can be smaller than the size of the device - * screen. + * screen. System decoration handling may vary depending on API level: + *

      + *
    • API level 35 and above, the window size will be returned. + *
    • API level 34 and below, the window size minus system decoration areas and + * display cutout is returned. + *
    *
  • If metrics are requested from a non-activity context (for example, the application * context, where the WindowManager is accessed by * {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index fc7a65dbdc41a14e365f87df9185d6f309cd3e5e..bb233d2711de05ab0f41a53e27c36a1ff9f1be81 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -207,6 +207,8 @@ public abstract class DisplayEventReceiver { // reasonable timestamps. public int frameTimelinesLength = 1; + public int numberQueuedBuffers = 0; + VsyncEventData() { frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY]; for (int i = 0; i < frameTimelines.length; i++) { @@ -217,11 +219,13 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex, - int frameTimelinesLength, long frameInterval) { + int frameTimelinesLength, long frameInterval, + int numberQueuedBuffers) { this.frameTimelines = frameTimelines; this.preferredFrameTimelineIndex = preferredFrameTimelineIndex; this.frameTimelinesLength = frameTimelinesLength; this.frameInterval = frameInterval; + this.numberQueuedBuffers = numberQueuedBuffers; } void copyFrom(VsyncEventData other) { @@ -231,6 +235,7 @@ public abstract class DisplayEventReceiver { for (int i = 0; i < frameTimelines.length; i++) { frameTimelines[i].copyFrom(other.frameTimelines[i]); } + numberQueuedBuffers = other.numberQueuedBuffers; } public FrameTimeline preferredFrameTimeline() { diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index 9e25a3e18c0c8d7aa60322f7c947361ee3e9c86b..58b2a67ec69ef9bc2c572d6a58b212c18b34d470 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -18,10 +18,13 @@ package android.view; import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import com.android.window.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -176,6 +179,16 @@ public final class FrameMetrics { **/ public static final int DEADLINE = 13; + /** + * Metric identifier for the frame's VSync identifier. + *

    + * The id that corresponds to the chosen frame timeline, used to correlate a frame produced + * by HWUI with the timeline data from the compositor. + *

    + */ + @FlaggedApi(Flags.FLAG_JANK_API) + public static final int FRAME_TIMELINE_VSYNC_ID = 14; + /** * Identifiers for metrics available for each frame. * @@ -337,7 +350,8 @@ public final class FrameMetrics { * @return the value of the metric or -1 if it is not available. */ public long getMetric(@Metric int id) { - if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) { + if (id < UNKNOWN_DELAY_DURATION + || id > (Flags.jankApi() ? FRAME_TIMELINE_VSYNC_ID : DEADLINE)) { return -1; } @@ -351,6 +365,8 @@ public final class FrameMetrics { return mTimingData[Index.INTENDED_VSYNC]; } else if (id == VSYNC_TIMESTAMP) { return mTimingData[Index.VSYNC]; + } else if (id == FRAME_TIMELINE_VSYNC_ID) { + return mTimingData[Index.FRAME_TIMELINE_VSYNC_ID]; } int durationsIdx = 2 * id; @@ -358,4 +374,3 @@ public final class FrameMetrics { - mTimingData[DURATIONS[durationsIdx]]; } } - diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java index 0001176220b58a90808e4905c3801959598703ef..c3fb855eb1ffe7de6e8a6459392a48b10d97e15c 100644 --- a/core/java/android/view/HapticScrollFeedbackProvider.java +++ b/core/java/android/view/HapticScrollFeedbackProvider.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.flags.Flags.dynamicViewRotaryHapticsConfiguration; + import android.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; @@ -41,13 +43,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private final View mView; private final ViewConfiguration mViewConfig; - /** - * Flag to disable the logic in this class if the View-based scroll haptics implementation is - * enabled. If {@code false}, this class will continue to run despite the View's scroll - * haptics implementation being enabled. This value should be set to {@code true} when this - * class is directly used by the View class. - */ - private final boolean mDisabledIfViewPlaysScrollHaptics; + /** Whether or not this provider is being used directly by the View class. */ + private final boolean mIsFromView; // Info about the cause of the latest scroll event. @@ -65,17 +62,23 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private boolean mHapticScrollFeedbackEnabled = false; public HapticScrollFeedbackProvider(@NonNull View view) { - this(view, ViewConfiguration.get(view.getContext()), - /* disabledIfViewPlaysScrollHaptics= */ true); + this(view, ViewConfiguration.get(view.getContext()), /* isFromView= */ false); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public HapticScrollFeedbackProvider( - View view, ViewConfiguration viewConfig, boolean disabledIfViewPlaysScrollHaptics) { + View view, ViewConfiguration viewConfig, boolean isFromView) { mView = view; mViewConfig = viewConfig; - mDisabledIfViewPlaysScrollHaptics = disabledIfViewPlaysScrollHaptics; + mIsFromView = isFromView; + if (dynamicViewRotaryHapticsConfiguration() && !isFromView) { + // Disable the View class's rotary scroll feedback logic if this provider is not being + // directly used by the View class. This is to avoid double rotary scroll feedback: + // one from the View class, and one from this provider instance (i.e. mute the View + // class's rotary feedback and enable this provider). + view.disableRotaryScrollFeedback(); + } } @Override @@ -151,7 +154,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { mAxis = axis; mDeviceId = deviceId; - if (mDisabledIfViewPlaysScrollHaptics + if (!dynamicViewRotaryHapticsConfiguration() + && !mIsFromView && (source == InputDevice.SOURCE_ROTARY_ENCODER) && mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) { mHapticScrollFeedbackEnabled = false; diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 97148969e17f0d1509a1a5df0ebbeb1e0b804017..2d2f79d760083a18f51f2570c0515ebf7f21506f 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.UiThread; import android.util.Log; import android.util.proto.ProtoOutputStream; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import com.android.internal.inputmethod.InputMethodDebug; @@ -150,6 +151,17 @@ public final class ImeFocusController { if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) { return InputMethodManager.DISPATCH_NOT_HANDLED; } + if (Flags.refactorInsetsController() && event instanceof KeyEvent keyEvent + && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) { + final var insetsController = mViewRootImpl.getInsetsController(); + if (insetsController.getAnimationType(WindowInsets.Type.ime()) + == InsetsController.ANIMATION_TYPE_HIDE + || insetsController.isPredictiveBackImeHideAnimInProgress()) { + // if there is an ongoing hide animation, the back event should not be dispatched + // to the IME. + return InputMethodManager.DISPATCH_NOT_HANDLED; + } + } final InputMethodManager imm = mViewRootImpl.mContext.getSystemService(InputMethodManager.class); if (imm == null) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 26ca813a9caac53540c8edeb2744afa51f7cc828..b0813f3a98f69dc962697aa7f6b2ed84d70752b7 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1910,7 +1910,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mImeSourceConsumer.onWindowFocusLost(); } - @VisibleForTesting + /** Returns the current {@link AnimationType} of an {@link InsetsType}. */ + @VisibleForTesting(visibility = PACKAGE) public @AnimationType int getAnimationType(@InsetsType int type) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 5a28d5f7fc019d1170607274ee33472289e001df..80ae3c3d4e7306dae4e16e20fd87a5dfd32d75cc 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -437,16 +437,16 @@ public class ScaleGestureDetector { } } - /** - * Return whether the quick scale gesture, in which the user performs a double tap followed by a - * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. - */ + /** + * Return whether the quick scale gesture, in which the user performs a double tap followed by a + * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. + */ public boolean isQuickScaleEnabled() { return mQuickScaleEnabled; } /** - * Sets whether the associates {@link OnScaleGestureListener} should receive + * Sets whether the associated {@link OnScaleGestureListener} should receive * onScale callbacks when the user uses a stylus and presses the button. * Note that this is enabled by default if the app targets API 23 and newer. * diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 206c7375608814709b273947665bdc5f8f1dfab1..d56768d2db031e26c1b57067ccda5a4f9aa9e635 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,6 +22,7 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.view.flags.Flags.bufferStuffingRecovery; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; @@ -410,8 +411,19 @@ public final class SurfaceControl implements Parcelable { /** * Jank information to be fed back via {@link OnJankDataListener}. - * @hide + *

    + * Apps may register a {@link OnJankDataListener} to get periodic batches of jank classification + * data from the ( + * composer regarding rendered frames. A frame is considered janky if it did not reach the + * display at the intended time, typically due to missing a rendering deadline. This API + * provides information that can be used to identify the root cause of the scheduling misses + * and provides overall frame scheduling statistics. + *

    + * This API can be used in conjunction with the {@link FrameMetrics} API by associating jank + * classification data with {@link FrameMetrics} data via the frame VSync id. */ + @FlaggedApi(Flags.FLAG_JANK_API) public static class JankData { /** @@ -428,29 +440,105 @@ public final class SurfaceControl implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface JankType {} - // No Jank + /** + * No jank detected, the frame was on time. + */ public static final int JANK_NONE = 0; - // Jank caused by the composer missing a deadline + + /** + * Bitmask for jank due to deadlines missed by the composer. + */ public static final int JANK_COMPOSER = 1 << 0; - // Jank caused by the application missing the composer's deadline + + /** + * Bitmask for jank due to deadlines missed by the application. + */ public static final int JANK_APPLICATION = 1 << 1; - // Jank due to other unknown reasons + + /** + * Bitmask for jank due to deadlines missed by other system components. + */ public static final int JANK_OTHER = 1 << 2; + private final long mFrameVsyncId; + private final @JankType int mJankType; + private final long mFrameIntervalNs; + private final long mScheduledAppFrameTimeNs; + private final long mActualAppFrameTimeNs; + + /** + * @hide + */ public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs, long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) { - this.frameVsyncId = frameVsyncId; - this.jankType = jankType; - this.frameIntervalNs = frameIntervalNs; - this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs; - this.actualAppFrameTimeNs = actualAppFrameTimeNs; - } - - public final long frameVsyncId; - public final @JankType int jankType; - public final long frameIntervalNs; - public final long scheduledAppFrameTimeNs; - public final long actualAppFrameTimeNs; + mFrameVsyncId = frameVsyncId; + mJankType = jankType; + mFrameIntervalNs = frameIntervalNs; + mScheduledAppFrameTimeNs = scheduledAppFrameTimeNs; + mActualAppFrameTimeNs = actualAppFrameTimeNs; + } + + /** + * Returns the id of the frame for this jank classification. + * + * @see FrameMetrics#FRAME_TIMELINE_VSYNC_ID + * @see Choreographer.FrameTimeline#getVsyncId + * @see Transaction#setFrameTimeline + * @return the frame id + */ + public long getVsyncId() { + return mFrameVsyncId; + } + + /** + * Returns the bitmask indicating the types of jank observed. + * + * @return the jank type bitmask + */ + public @JankType int getJankType() { + return mJankType; + } + + /** + * Returns the time between frame VSyncs in nanoseconds. + * + * @return the frame interval in ns + * @hide + */ + public long getFrameIntervalNanos() { + return mFrameIntervalNs; + } + + /** + * Returns the duration in nanoseconds the application was scheduled to use to render this + * frame. + *

    + * Note that this may be higher than the frame interval to allow for CPU/GPU + * parallelization of work. + * + * @return scheduled app time in ns + */ + public long getScheduledAppFrameTimeNanos() { + return mScheduledAppFrameTimeNs; + } + + /** + * Returns the actual time in nanoseconds taken by the application to render this frame. + * + * @return the actual app time in ns + */ + public long getActualAppFrameTimeNanos() { + return mActualAppFrameTimeNs; + } + + @Override + public String toString() { + return "JankData{vsync=" + mFrameVsyncId + + ", jankType=0x" + Integer.toHexString(mJankType) + + ", frameInterval=" + mFrameIntervalNs + "ns" + + ", scheduledAppTime=" + mScheduledAppFrameTimeNs + "ns" + + ", actualAppTime=" + mActualAppFrameTimeNs + "ns}"; + } } /** @@ -458,12 +546,13 @@ public final class SurfaceControl implements Parcelable { * surface. * * @see JankData - * @see #addJankDataListener - * @hide + * @see #addOnJankDataListener */ + @FlaggedApi(Flags.FLAG_JANK_API) public interface OnJankDataListener { /** - * Called when new jank classifications are available. + * Called when new jank classifications are available. The listener is invoked out of band + * of the rendered frames with jank classification data for a batch of frames. */ void onJankDataAvailable(@NonNull List jankData); @@ -471,9 +560,22 @@ public final class SurfaceControl implements Parcelable { /** * Handle to a registered {@link OnJankDatalistener}. - * @hide */ + @FlaggedApi(Flags.FLAG_JANK_API) public static class OnJankDataListenerRegistration { + /** @hide */ + public static final OnJankDataListenerRegistration NONE = + new OnJankDataListenerRegistration() { + @Override + public void flush() {} + + @Override + public void removeAfter(long afterVsync) {} + + @Override + public void release() {} + }; + private final long mNativeObject; private static final NativeAllocationRegistry sRegistry = @@ -484,6 +586,11 @@ public final class SurfaceControl implements Parcelable { private final Runnable mFreeNativeResources; private boolean mRemoved = false; + private OnJankDataListenerRegistration() { + mNativeObject = 0; + mFreeNativeResources = () -> {}; + } + OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) { mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener); mFreeNativeResources = (mNativeObject == 0) ? () -> {} : @@ -499,10 +606,17 @@ public final class SurfaceControl implements Parcelable { } /** - * Request the removal of the registered listener after the VSync with the provided ID. Use - * a value <= 0 for afterVsync to remove the listener immediately. The given listener will - * not be removed before the given VSync, but may still reveive data for frames past the - * provided VSync. + * Schedule the removal of the registered listener after the frame with the provided id. + *

    + * Because jank classification is only possible after frames have been displayed, the + * callbacks are always delayed. To ensure receipt of all jank classification data, an + * application can schedule the removal to happen no sooner than after the data for the + * frame with the provided id has been provided. + *

    + * Use a value <= 0 for afterVsync to remove the listener immediately, ensuring no future + * callbacks. + * + * @param afterVsync the id of the Vsync after which to remove the listener */ public void removeAfter(long afterVsync) { mRemoved = true; @@ -511,6 +625,7 @@ public final class SurfaceControl implements Parcelable { /** * Free the native resources associated with the listener registration. + * @hide */ public void release() { if (!mRemoved) { @@ -662,6 +777,13 @@ public final class SurfaceControl implements Parcelable { */ public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000; + /** + * Indicates that the SurfaceControl should recover from buffer stuffing when + * possible. This is the case when the SurfaceControl is a ViewRootImpl. + * @hide + */ + public static final int RECOVERABLE_FROM_BUFFER_STUFFING = 0x00002000; + /** * Surface creation flag: Creates a surface where color components are interpreted * as "non pre-multiplied" by their alpha channel. Of course this flag is @@ -4444,14 +4566,31 @@ public final class SurfaceControl implements Parcelable { return this; } - /** @hide */ + /** + * Sets the Luts for the layer. + * + *

    The function also allows to clear previously applied lut(s). To do this, + * set the displayluts to be either {@code nullptr} or + * an empty {@link android.hardware.DisplayLuts} instance. + * + * @param sc The SurfaceControl to update + * + * @param displayLuts The selected Lut(s) + * + * @return this + * @see DisplayLuts + */ + @FlaggedApi(android.hardware.flags.Flags.FLAG_LUTS_API) public @NonNull Transaction setLuts(@NonNull SurfaceControl sc, - @NonNull DisplayLuts displayLuts) { + @Nullable DisplayLuts displayLuts) { checkPreconditions(sc); - - nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(), - displayLuts.getOffsets(), displayLuts.getLutDimensions(), - displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys()); + if (displayLuts != null && displayLuts.valid()) { + nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(), + displayLuts.getOffsets(), displayLuts.getLutDimensions(), + displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys()); + } else { + nativeSetLuts(mNativeObject, sc.mNativeObject, null, null, null, null, null); + } return this; } @@ -4867,6 +5006,23 @@ public final class SurfaceControl implements Parcelable { nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos); return this; } + + /** + * Specifies that the SurfaceControl is a buffer producer that should recover from buffer + * stuffing, meaning that the SurfaceControl is a ViewRootImpl. + * + * @hide + */ + @NonNull + public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) { + if (bufferStuffingRecovery()) { + checkPreconditions(sc); + nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING, + RECOVERABLE_FROM_BUFFER_STUFFING); + } + return this; + } + /** * Writes the transaction to parcel, clearing the transaction as if it had been applied so * it can be used to store future transactions. It's the responsibility of the parcel diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index fa06831f65141854f45678b0cd76b11ebdd870e7..049189f8af8d4db876b234b4d6b16bcce32ba874 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -65,6 +65,7 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS; +import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW; import static java.lang.Math.max; @@ -91,6 +92,8 @@ import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UiThread; import android.app.PendingIntent; +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.ClipData; @@ -5550,10 +5553,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Flag indicating that this drag will result in the caller activity's task to be hidden for the - * duration of the drag, this means that the source activity will not receive drag events for - * the current drag gesture. Only the current voice interaction service may use this flag. - * @hide + * duration of the drag, which means that the source activity will not receive drag events for + * the current drag gesture. Only the current + * {@link android.service.voice.VoiceInteractionService} may use this flag. */ + @FlaggedApi(FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW) public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14; /** @@ -16750,9 +16754,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; } } - final boolean processForRotaryScrollHaptics = - isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0); - if (processForRotaryScrollHaptics) { + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; } @@ -16769,7 +16771,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually // happens. Some views may return false from `onGenericMotionEvent` even if they have done // scrolling, so disregard the return value when processing for scroll haptics. - if (processForRotaryScrollHaptics) { + // Check for `PFLAG4_ROTARY_HAPTICS_ENABLED` again, because the View implementation may + // call `disableRotaryScrollFeedback` in `onGenericMotionEvent`, which could change the + // value of `PFLAG4_ROTARY_HAPTICS_ENABLED`. + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) { doRotaryProgressForScrollHaptics(event); } else { @@ -18712,7 +18717,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private HapticScrollFeedbackProvider getScrollFeedbackProvider() { if (mScrollFeedbackProvider == null) { mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this, - ViewConfiguration.get(mContext), /* disabledIfViewPlaysScrollHaptics= */ false); + ViewConfiguration.get(mContext), /* isFromView= */ true); } return mScrollFeedbackProvider; } @@ -18741,6 +18746,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Disables the rotary scroll feedback implementation of the View class. + * + *

    Note that this does NOT disable all rotary scroll feedback; it just disables the logic + * implemented within the View class. The child implementation of the View may implement its own + * rotary scroll feedback logic or use {@link ScrollFeedbackProvider} to generate rotary scroll + * feedback. + */ + void disableRotaryScrollFeedback() { + // Force set PFLAG4_ROTARY_HAPTICS_DETERMINED to avoid recalculating + // PFLAG4_ROTARY_HAPTICS_ENABLED under any circumstance. + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_ENABLED; + } + /** * This is called in response to an internal scroll in this view (i.e., the * view scrolled its own contents). This is typically as a result of @@ -34420,4 +34440,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean getSelfRequestedFrameRateFlag() { return (mPrivateFlags4 & PFLAG4_SELF_REQUESTED_FRAME_RATE) != 0; } + + /** + * Called from apps when they want to report jank stats to the system. + * @param appJankStats the stats that will be merged with the stats collected by the system. + */ + @FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void reportAppJankStats(@NonNull AppJankStats appJankStats) { + getRootView().reportAppJankStats(appJankStats); + } + + /** + * Called by widgets to get a reference to JankTracker in order to update states. + * @hide + */ + public @Nullable JankTracker getJankTracker() { + return getRootView().getJankTracker(); + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 75d2da1b70e4cf963e8411d729a9f12964b3c80d..19d3dc4df04e83784a0d7f1c712735e0fb52f123 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -140,6 +140,8 @@ import android.accessibilityservice.AccessibilityService; import android.animation.AnimationHandler; import android.animation.LayoutTransition; import android.annotation.AnyThread; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; @@ -1250,7 +1252,6 @@ public final class ViewRootImpl implements ViewParent, mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName); mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); - mLocation.fillInStackTrace(); mWidth = -1; mHeight = -1; mDirty = new Rect(); @@ -2515,6 +2516,11 @@ public final class ViewRootImpl implements ViewParent, public void notifyInsetsAnimationRunningStateChanged(boolean running) { if (sToolkitSetFrameRateReadOnlyFlagValue) { mInsetsAnimationRunning = running; + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, + TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)", + Boolean.toString(running))); + } } } @@ -2758,6 +2764,9 @@ public final class ViewRootImpl implements ViewParent, // Only call transferFrom if the surface has changed to prevent inc the generation ID and // causing EGL resources to be recreated. mSurface.transferFrom(blastSurface); + + // Since the SurfaceControl is a VRI, indicate that it can recover from buffer stuffing. + mTransaction.setRecoverableFromBufferStuffing(mSurfaceControl).applyAsyncUnsafe(); } private void setBoundsLayerCrop(Transaction t) { @@ -11896,6 +11905,20 @@ public final class ViewRootImpl implements ViewParent, return mAttachInfo.mWindowToken; } + /** + * {@inheritDoc} + */ + @NonNull + @Override + @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API) + public SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull SurfaceControl.OnJankDataListener listener) { + SurfaceControl.OnJankDataListener wrapped = (data) -> + executor.execute(() -> listener.onJankDataAvailable(data)); + return mSurfaceControl.addOnJankDataListener(wrapped); + } + /** * Class for managing the accessibility interaction connection * based on the global accessibility state. diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 1af9387e6fbdafd6f271c418aabcb2bfa1771e7a..1be7f4849f07a1b7291191643b82219ca5a528fa 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -17,6 +17,7 @@ package android.view; import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; +import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS; import android.annotation.FlaggedApi; import android.annotation.NonNull; @@ -79,27 +80,24 @@ public abstract class ViewStructure { * Key used for writing the type of the view that generated the virtual structure of its * children. * - * For example, if the virtual structure is generated by a webview, the value would be + *

    For example, if the virtual structure is generated by a webview, the value would be * "WebView". If the virtual structure is generated by a compose view, then the value would be * "ComposeView". The value is of type String. * - * This value is added to mainly help with debugging purpose. - * - * @hide + *

    This value is added to mainly help with debugging purpose. */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; - /** * Key used for specifying the version of the view that generated the virtual structure for * itself and its children * - * For example, if the virtual structure is generated by a webview of version "104.0.5112.69", - * then the value should be "104.0.5112.69" - * - * @hide + *

    For example, if the virtual structure is generated by a webview of version + * "104.0.5112.69", then the value should be "104.0.5112.69" */ + @FlaggedApi(FLAG_AUTOFILL_W_METRICS) public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 8bcc9de118e21549d7f85e6438c74c9e59f2c931..12af692a45568753b368a29a344c4299a9fa6a28 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -107,8 +107,8 @@ public final class WindowMetrics { * and display cutout areas depending on the calling context and target SDK level. Please refer * to {@link Display#getSize(Point)} for details. *

    - * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be - * obtained by using: + * The following code snippet shows how to get the bounds excluding navigation bars and display + * cutout: *

          * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
          * // Gets all excluding insets
    diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
    index 6b608582c1dd18b27f14a90ed084c080391c3564..bd277784c1d3de1a20746f4488a5f48f448b5658 100644
    --- a/core/java/android/view/autofill/AutofillId.java
    +++ b/core/java/android/view/autofill/AutofillId.java
    @@ -15,6 +15,9 @@
      */
     package android.view.autofill;
     
    +import static android.service.autofill.Flags.FLAG_AUTOFILL_W_METRICS;
    +
    +import android.annotation.FlaggedApi;
     import android.annotation.NonNull;
     import android.annotation.Nullable;
     import android.annotation.TestApi;
    @@ -111,11 +114,42 @@ public final class AutofillId implements Parcelable {
             return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION);
         }
     
    -    /** @hide */
    +    /**
    +     * Returns the assigned unique identifier of this AutofillID.
    +     *
    +     * See @link{android.view.View#getAutofillId()} for more information on
    +     * how this is generated for native Views.
    +     */
    +    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
         public int getViewId() {
             return mViewId;
         }
     
    +    /**
    +     * Gets the virtual id. This is set if the view is a virtual view, most commonly set if the View
    +     * is of {@link android.webkit.WebView}.
    +     */
    +    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
    +    public int getAutofillVirtualId() {
    +        return mVirtualIntId;
    +    }
    +
    +    /** Checks whether this AutofillId represents a virtual view. */
    +    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
    +    public boolean isVirtual() {
    +        return !isNonVirtual();
    +    }
    +
    +    /**
    +     * Checks if this node is generate as part of a {@link android.app.assist.AssistStructure}. This
    +     * will usually return true if it should be used by an autofill service provider, and false
    +     * otherwise.
    +     */
    +    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
    +    public boolean isInAutofillSession() {
    +        return hasSession();
    +    }
    +
         /**
          * Gets the virtual child id.
          *
    @@ -181,7 +215,12 @@ public final class AutofillId implements Parcelable {
             return (mFlags & FLAG_HAS_SESSION) != 0;
         }
     
    -    /** @hide */
    +    /**
    +     * Used to get the Session identifier associated with this AutofillId.
    +     *
    +     * @return a non-zero integer if {@link #isInAutofillSession()} returns true
    +     */
    +    @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
         public int getSessionId() {
             return mSessionId;
         }
    diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
    index 1a45939f65b626dd74bc527b7270f4d7c78138e7..52c5af8889ec494b81e3e65d583bcd86d94c55eb 100644
    --- a/core/java/android/view/autofill/AutofillManager.java
    +++ b/core/java/android/view/autofill/AutofillManager.java
    @@ -25,12 +25,14 @@ import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD
     import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
     import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
     import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
    +import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
     import static android.view.ContentInfo.SOURCE_AUTOFILL;
     import static android.view.autofill.Helper.sDebug;
     import static android.view.autofill.Helper.sVerbose;
     import static android.view.autofill.Helper.toList;
     
     import android.accessibilityservice.AccessibilityServiceInfo;
    +import android.annotation.FlaggedApi;
     import android.annotation.IntDef;
     import android.annotation.NonNull;
     import android.annotation.Nullable;
    @@ -1607,7 +1609,12 @@ public final class AutofillManager {
          *             the virtual view in the host view.
          *
          * @throws IllegalArgumentException if the {@code infos} was empty
    +     *
    +     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
    +     * by the framework and the autofill provider.
          */
    +    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
    +    @Deprecated
         public void notifyVirtualViewsReady(
                 @NonNull View view, @NonNull SparseArray infos) {
             Objects.requireNonNull(infos);
    @@ -4034,8 +4041,13 @@ public final class AutofillManager {
          *             receiving a focus event. The autofill suggestions shown will include content for
          *             related views as well.
          * @return {@code true} if the autofill dialog is being shown
    +     *
    +     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
    +     * by the framework and the autofill provider.
          */
         // TODO(b/210926084): Consider whether to include the one-time show logic within this method.
    +    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
    +    @Deprecated
         public boolean showAutofillDialog(@NonNull View view) {
             Objects.requireNonNull(view);
             if (shouldShowAutofillDialog(view, view.getAutofillId())) {
    @@ -4073,7 +4085,12 @@ public final class AutofillManager {
          *            suggestions.
          * @param virtualId id identifying the virtual view inside the host view.
          * @return {@code true} if the autofill dialog is being shown
    +     *
    +     * @deprecated This function will not do anything. Showing fill dialog is now fully controlled
    +     * by the framework and the autofill provider.
          */
    +    @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS)
    +    @Deprecated
         public boolean showAutofillDialog(@NonNull View view, int virtualId) {
             Objects.requireNonNull(view);
             if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) {
    diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
    index 658aa29a3cc65bb0dceffab21103a35d354b1a08..ebda4d472b0d91b739c6d199d7aafceaf4cc2fdb 100644
    --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
    +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
    @@ -23,3 +23,10 @@ flag {
         bug: "331830899"
         is_fixed_read_only: true
     }
    +
    +flag {
    +    namespace: "wear_frameworks"
    +    name: "dynamic_view_rotary_haptics_configuration"
    +    description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics."
    +    bug: "377998870"
    +}
    diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
    index 1b86f96d7eb7e557922e1aab3fcf835b1b8d3851..3b6343e7c4ae88b424abe08dd550a930347fa6ee 100644
    --- a/core/java/android/view/flags/view_flags.aconfig
    +++ b/core/java/android/view/flags/view_flags.aconfig
    @@ -132,4 +132,12 @@ flag {
         description: "Use refactored round scrollbar."
         bug: "333417898"
         is_fixed_read_only: true
    +}
    +
    +flag {
    +    name: "buffer_stuffing_recovery"
    +    namespace: "window_surfaces"
    +    description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
    +    bug: "294922229"
    +    is_fixed_read_only: true
     }
    \ No newline at end of file
    diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
    index fb3e0831fdc9bcc9b346db2acf89639d1d96c2b0..afe195c48b013dfa0bc6466450f45080f8ae3200 100644
    --- a/core/java/android/view/inputmethod/EditorInfo.java
    +++ b/core/java/android/view/inputmethod/EditorInfo.java
    @@ -24,6 +24,7 @@ import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME;
     import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
     import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
     import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED;
    +import static android.view.inputmethod.Flags.FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO;
     
     import android.annotation.FlaggedApi;
     import android.annotation.IntDef;
    @@ -470,12 +471,10 @@ public class EditorInfo implements InputType, Parcelable {
         public String packageName;
     
         /**
    -     * Autofill Id for the field that's currently on focus.
    -     *
    -     * 

    Marked as hide since it's only used by framework.

    - * @hide + * Autofill Id for the field that's currently on focus. See link {@link AutofillId} for more + * details. It is set by {@link View#getAutofillId()} */ - public AutofillId autofillId; + private AutofillId autofillId; /** * Identifier for the editor's field. This is optional, and may be @@ -524,7 +523,6 @@ public class EditorInfo implements InputType, Parcelable { @Nullable public LocaleList hintLocales = null; - /** * List of acceptable MIME types for * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}. @@ -759,6 +757,30 @@ public class EditorInfo implements InputType, Parcelable { return mIsStylusHandwritingEnabled; } + private boolean mWritingToolsEnabled = true; + + /** + * Returns {@code true} when an {@code Editor} has writing tools enabled. + * {@code true} by default for all editors. Toolkits can optionally disable them where not + * relevant e.g. passwords, number input, etc. + * @see #setWritingToolsEnabled(boolean) + */ + @FlaggedApi(Flags.FLAG_WRITING_TOOLS) + public boolean isWritingToolsEnabled() { + return mWritingToolsEnabled; + } + + /** + * Set {@code false} if {@code Editor} opts-out of writing tools, that enable IMEs to replace + * text with generative AI text. + * @param enabled set {@code true} to enabled or {@code false to disable} support. + * @see #isWritingToolsEnabled() + */ + @FlaggedApi(Flags.FLAG_WRITING_TOOLS) + public void setWritingToolsEnabled(boolean enabled) { + mWritingToolsEnabled = enabled; + } + /** * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no * matter what user ID the calling process has. @@ -1199,6 +1221,28 @@ public class EditorInfo implements InputType, Parcelable { mInitialToolType = toolType; } + /** + * Returns the {@link AutofillId} of the view that this {@link EditorInfo} is associated with. + * The value is filled in with the result of {@link android.view.View#getAutofillId() + * View.getAutofillId()} on the view that is being edited. + * + * Note: For virtual view(e.g. Compose or Webview), default behavior is the autofillId is the id + * of the container view, unless the virtual view provider sets the virtual id when the + * InputMethodManager calls {@link android.view.View#onCreateInputConnection()} on the container + * view. + */ + @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO) + @Nullable + public AutofillId getAutofillId() { + return autofillId; + } + + /** Sets the {@link AutofillId} of the view that this {@link EditorInfo} is associated with. */ + @FlaggedApi(FLAG_PUBLIC_AUTOFILL_ID_IN_EDITORINFO) + public void setAutofillId(@Nullable AutofillId autofillId) { + this.autofillId = autofillId; + } + /** * Export the state of {@link EditorInfo} into a protocol buffer output stream. * @@ -1255,6 +1299,7 @@ public class EditorInfo implements InputType, Parcelable { + InputMethodDebug.handwritingGestureTypeFlagsToString( mSupportedHandwritingGesturePreviewTypes)); pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled); + pw.println(prefix + "writingToolsEnabled=" + mWritingToolsEnabled); pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes)); if (targetInputMethodUser != null) { pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier()); @@ -1335,6 +1380,7 @@ public class EditorInfo implements InputType, Parcelable { } dest.writeStringArray(contentMimeTypes); UserHandle.writeToParcel(targetInputMethodUser, dest); + dest.writeBoolean(mWritingToolsEnabled); } /** @@ -1375,6 +1421,7 @@ public class EditorInfo implements InputType, Parcelable { res.hintLocales = hintLocales.isEmpty() ? null : hintLocales; res.contentMimeTypes = source.readStringArray(); res.targetInputMethodUser = UserHandle.readFromParcel(source); + res.mWritingToolsEnabled = source.readBoolean(); return res; } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 73f9d9fc23dc90c2b7bfa872a06dcf39f1df7ccc..6303c7637a59c0acbc01d6b45530a6808af6aae2 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2471,6 +2471,11 @@ public final class InputMethodManager { return; } + if (Flags.refactorInsetsController()) { + showSoftInput(rootView, statsToken, flags, resultReceiver, reason); + return; + } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread. @@ -5174,7 +5179,7 @@ public final class InputMethodManager { // system can verify the consistency between the uid of this process and package name passed // from here. See comment of Context#getOpPackageName() for details. editorInfo.packageName = servedView.getContext().getOpPackageName(); - editorInfo.autofillId = servedView.getAutofillId(); + editorInfo.setAutofillId(servedView.getAutofillId()); editorInfo.fieldId = servedView.getId(); final InputConnection ic = servedView.onCreateInputConnection(editorInfo); if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic); @@ -5183,7 +5188,7 @@ public final class InputMethodManager { // This ensures that even disconnected EditorInfos have well-defined attributes, // making them consistently and straightforwardly comparable. if (ic == null) { - editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID; + editorInfo.setAutofillId(AutofillId.NO_AUTOFILL_ID); editorInfo.fieldId = 0; } return new Pair<>(ic, editorInfo); diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index edd9d6cff799769a5cef5aab91b0b89e40316cec..deaf95797127b9c8ed5770e9d00ffb2e1aa41a82 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -165,4 +165,22 @@ flag { description: "Writing tools API" bug: "373788889" is_fixed_read_only: true -} \ No newline at end of file +} + +flag { + name: "public_autofill_id_in_editorinfo" + is_exported: true + namespace: "input_method" + description: "Guarding public API autofillId in editor info" + bug: "342672560" + is_fixed_read_only: true +} + +flag { + name: "adaptive_handwriting_bounds" + is_exported: true + namespace: "input_method" + description: "Feature flag for adaptively increasing handwriting bounds." + bug: "350047836" + is_fixed_read_only: true +} diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 877fa74138feb2b3fcd87fc3164616037729ff9e..1baf3b82ca500119992bd77b6fa555ec2a24a199 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -20,6 +20,8 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; @@ -552,6 +554,23 @@ public class WebChromeClient { * Parameters used in the {@link #onShowFileChooser} method. */ public static abstract class FileChooserParams { + /** + * Enable File System Access for webview. + * + * File System Access JS APIs window.showOpenFileChooser(), showDirectoryChooser(), and + * showSaveFilePicker() will invoke #onFileChooser(). Additional MODE_OPEN_FOLDER will be + * returned by #getMode(), #getIntent() will return ACTION_OPEN_DOCUMENT, + * ACTION_OPEN_DOCUMENT_TREE, and ACTION_CREATE_DOCUMENT rather than the current + * ACTION_GET_CONTENT, and new function #getPermissionMode() will be available. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) + @FlaggedApi(android.webkit.Flags.FLAG_FILE_SYSTEM_ACCESS) + @SystemApi + public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L; + /** @hide */ @IntDef(prefix = { "MODE_" }, value = { MODE_OPEN, diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d7750bd412a3790dce7a04f3eb9009a1d4518dbe..cb70466fcd81c32e39894178876f0127f1761cc1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -106,6 +106,7 @@ import android.os.Parcelable; import android.os.ParcelableParcel; import android.os.Process; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.text.BoringLayout; @@ -9229,174 +9230,179 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected void onDraw(Canvas canvas) { - restartMarqueeIfNeeded(); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onDraw"); + try { + restartMarqueeIfNeeded(); - // Draw the background for this view - super.onDraw(canvas); - - final int compoundPaddingLeft = getCompoundPaddingLeft(); - final int compoundPaddingTop = getCompoundPaddingTop(); - final int compoundPaddingRight = getCompoundPaddingRight(); - final int compoundPaddingBottom = getCompoundPaddingBottom(); - final int scrollX = mScrollX; - final int scrollY = mScrollY; - final int right = mRight; - final int left = mLeft; - final int bottom = mBottom; - final int top = mTop; - final boolean isLayoutRtl = isLayoutRtl(); - final int offset = getHorizontalOffsetForDrawables(); - final int leftOffset = isLayoutRtl ? 0 : offset; - final int rightOffset = isLayoutRtl ? offset : 0; + // Draw the background for this view + super.onDraw(canvas); - final Drawables dr = mDrawables; - if (dr != null) { - /* - * Compound, not extended, because the icon is not clipped - * if the text height is smaller. - */ + final int compoundPaddingLeft = getCompoundPaddingLeft(); + final int compoundPaddingTop = getCompoundPaddingTop(); + final int compoundPaddingRight = getCompoundPaddingRight(); + final int compoundPaddingBottom = getCompoundPaddingBottom(); + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final int right = mRight; + final int left = mLeft; + final int bottom = mBottom; + final int top = mTop; + final boolean isLayoutRtl = isLayoutRtl(); + final int offset = getHorizontalOffsetForDrawables(); + final int leftOffset = isLayoutRtl ? 0 : offset; + final int rightOffset = isLayoutRtl ? offset : 0; - int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; - int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; + final Drawables dr = mDrawables; + if (dr != null) { + /* + * Compound, not extended, because the icon is not clipped + * if the text height is smaller. + */ - // IMPORTANT: The coordinates computed are also used in invalidateDrawable() - // Make sure to update invalidateDrawable() when changing this code. - if (dr.mShowing[Drawables.LEFT] != null) { - canvas.save(); - canvas.translate(scrollX + mPaddingLeft + leftOffset, - scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); - dr.mShowing[Drawables.LEFT].draw(canvas); - canvas.restore(); - } + int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; + int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; + + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. + if (dr.mShowing[Drawables.LEFT] != null) { + canvas.save(); + canvas.translate(scrollX + mPaddingLeft + leftOffset, + scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); + dr.mShowing[Drawables.LEFT].draw(canvas); + canvas.restore(); + } - // IMPORTANT: The coordinates computed are also used in invalidateDrawable() - // Make sure to update invalidateDrawable() when changing this code. - if (dr.mShowing[Drawables.RIGHT] != null) { - canvas.save(); - canvas.translate(scrollX + right - left - mPaddingRight - - dr.mDrawableSizeRight - rightOffset, - scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); - dr.mShowing[Drawables.RIGHT].draw(canvas); - canvas.restore(); - } + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. + if (dr.mShowing[Drawables.RIGHT] != null) { + canvas.save(); + canvas.translate(scrollX + right - left - mPaddingRight + - dr.mDrawableSizeRight - rightOffset, + scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); + dr.mShowing[Drawables.RIGHT].draw(canvas); + canvas.restore(); + } - // IMPORTANT: The coordinates computed are also used in invalidateDrawable() - // Make sure to update invalidateDrawable() when changing this code. - if (dr.mShowing[Drawables.TOP] != null) { - canvas.save(); - canvas.translate(scrollX + compoundPaddingLeft - + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); - dr.mShowing[Drawables.TOP].draw(canvas); - canvas.restore(); - } + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. + if (dr.mShowing[Drawables.TOP] != null) { + canvas.save(); + canvas.translate(scrollX + compoundPaddingLeft + + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); + dr.mShowing[Drawables.TOP].draw(canvas); + canvas.restore(); + } - // IMPORTANT: The coordinates computed are also used in invalidateDrawable() - // Make sure to update invalidateDrawable() when changing this code. - if (dr.mShowing[Drawables.BOTTOM] != null) { - canvas.save(); - canvas.translate(scrollX + compoundPaddingLeft - + (hspace - dr.mDrawableWidthBottom) / 2, - scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); - dr.mShowing[Drawables.BOTTOM].draw(canvas); - canvas.restore(); + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. + if (dr.mShowing[Drawables.BOTTOM] != null) { + canvas.save(); + canvas.translate(scrollX + compoundPaddingLeft + + (hspace - dr.mDrawableWidthBottom) / 2, + scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); + dr.mShowing[Drawables.BOTTOM].draw(canvas); + canvas.restore(); + } } - } - int color = mCurTextColor; + int color = mCurTextColor; - if (mLayout == null) { - assumeLayout(); - } + if (mLayout == null) { + assumeLayout(); + } + + Layout layout = mLayout; - Layout layout = mLayout; + if (mHint != null && !mHideHint && mText.length() == 0) { + if (mHintTextColor != null) { + color = mCurHintTextColor; + } - if (mHint != null && !mHideHint && mText.length() == 0) { - if (mHintTextColor != null) { - color = mCurHintTextColor; + layout = mHintLayout; } - layout = mHintLayout; - } + mTextPaint.setColor(color); + mTextPaint.drawableState = getDrawableState(); - mTextPaint.setColor(color); - mTextPaint.drawableState = getDrawableState(); + canvas.save(); + /* Would be faster if we didn't have to do this. Can we chop the + (displayable) text so that we don't need to do this ever? + */ - canvas.save(); - /* Would be faster if we didn't have to do this. Can we chop the - (displayable) text so that we don't need to do this ever? - */ + int extendedPaddingTop = getExtendedPaddingTop(); + int extendedPaddingBottom = getExtendedPaddingBottom(); - int extendedPaddingTop = getExtendedPaddingTop(); - int extendedPaddingBottom = getExtendedPaddingBottom(); + final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; + final int maxScrollY = mLayout.getHeight() - vspace; - final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; - final int maxScrollY = mLayout.getHeight() - vspace; + float clipLeft = compoundPaddingLeft + scrollX; + float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; + float clipRight = right - left - getCompoundPaddingRight() + scrollX; + float clipBottom = bottom - top + scrollY + - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); - float clipLeft = compoundPaddingLeft + scrollX; - float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; - float clipRight = right - left - getCompoundPaddingRight() + scrollX; - float clipBottom = bottom - top + scrollY - - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); + if (mShadowRadius != 0) { + clipLeft += Math.min(0, mShadowDx - mShadowRadius); + clipRight += Math.max(0, mShadowDx + mShadowRadius); - if (mShadowRadius != 0) { - clipLeft += Math.min(0, mShadowDx - mShadowRadius); - clipRight += Math.max(0, mShadowDx + mShadowRadius); + clipTop += Math.min(0, mShadowDy - mShadowRadius); + clipBottom += Math.max(0, mShadowDy + mShadowRadius); + } - clipTop += Math.min(0, mShadowDy - mShadowRadius); - clipBottom += Math.max(0, mShadowDy + mShadowRadius); - } + canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); - canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); + int voffsetText = 0; + int voffsetCursor = 0; - int voffsetText = 0; - int voffsetCursor = 0; + // translate in by our padding + /* shortcircuit calling getVerticaOffset() */ + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + voffsetText = getVerticalOffset(false); + voffsetCursor = getVerticalOffset(true); + } + canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); + + final int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); + if (isMarqueeFadeEnabled()) { + if (!mSingleLine && getLineCount() == 1 && canMarquee() + && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { + final int width = mRight - mLeft; + final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); + final float dx = mLayout.getLineRight(0) - (width - padding); + canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); + } - // translate in by our padding - /* shortcircuit calling getVerticaOffset() */ - if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { - voffsetText = getVerticalOffset(false); - voffsetCursor = getVerticalOffset(true); - } - canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); - - final int layoutDirection = getLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); - if (isMarqueeFadeEnabled()) { - if (!mSingleLine && getLineCount() == 1 && canMarquee() - && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { - final int width = mRight - mLeft; - final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); - final float dx = mLayout.getLineRight(0) - (width - padding); - canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); + if (mMarquee != null && mMarquee.isRunning()) { + final float dx = -mMarquee.getScroll(); + canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); + } } - if (mMarquee != null && mMarquee.isRunning()) { - final float dx = -mMarquee.getScroll(); - canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); - } - } + final int cursorOffsetVertical = voffsetCursor - voffsetText; - final int cursorOffsetVertical = voffsetCursor - voffsetText; + maybeUpdateHighlightPaths(); + // If there is a gesture preview highlight, then the selection or cursor is not drawn. + Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath(); + if (mEditor != null) { + mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, + mHighlightPaint, cursorOffsetVertical); + } else { + layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, + cursorOffsetVertical); + } - maybeUpdateHighlightPaths(); - // If there is a gesture preview highlight, then the selection or cursor is not drawn. - Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath(); - if (mEditor != null) { - mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, - mHighlightPaint, cursorOffsetVertical); - } else { - layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, - cursorOffsetVertical); - } + if (mMarquee != null && mMarquee.shouldDrawGhost()) { + final float dx = mMarquee.getGhostOffset(); + canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); + layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, + cursorOffsetVertical); + } - if (mMarquee != null && mMarquee.shouldDrawGhost()) { - final float dx = mMarquee.getGhostOffset(); - canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); - layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, - cursorOffsetVertical); + canvas.restore(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); } - - canvas.restore(); } @Override @@ -11254,192 +11260,201 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - int width; - int height; + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onMeasure"); + try { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); - BoringLayout.Metrics boring = UNKNOWN_BORING; - BoringLayout.Metrics hintBoring = UNKNOWN_BORING; + int width; + int height; - if (mTextDir == null) { - mTextDir = getTextDirectionHeuristic(); - } + BoringLayout.Metrics boring = UNKNOWN_BORING; + BoringLayout.Metrics hintBoring = UNKNOWN_BORING; - int des = -1; - boolean fromexisting = false; - final float widthLimit = (widthMode == MeasureSpec.AT_MOST) - ? (float) widthSize : Float.MAX_VALUE; - - if (widthMode == MeasureSpec.EXACTLY) { - // Parent has told us how big to be. So be it. - width = widthSize; - } else { - if (mLayout != null && mEllipsize == null) { - des = desired(mLayout, mUseBoundsForWidth); + if (mTextDir == null) { + mTextDir = getTextDirectionHeuristic(); } - if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, - isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), - mBoring); - if (boring != null) { - mBoring = boring; - } - } else { - fromexisting = true; - } + int des = -1; + boolean fromexisting = false; + final float widthLimit = (widthMode == MeasureSpec.AT_MOST) + ? (float) widthSize : Float.MAX_VALUE; - if (boring == null || boring == UNKNOWN_BORING) { - if (des < 0) { - des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, - mTransformed.length(), mTextPaint, mTextDir, widthLimit, - mUseBoundsForWidth)); - } - width = des; + if (widthMode == MeasureSpec.EXACTLY) { + // Parent has told us how big to be. So be it. + width = widthSize; } else { - if (mUseBoundsForWidth) { - RectF bbox = boring.getDrawingBoundingBox(); - float rightMax = Math.max(bbox.right, boring.width); - float leftMin = Math.min(bbox.left, 0); - width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin)); - } else { - width = boring.width; + if (mLayout != null && mEllipsize == null) { + des = desired(mLayout, mUseBoundsForWidth); } - } - - final Drawables dr = mDrawables; - if (dr != null) { - width = Math.max(width, dr.mDrawableWidthTop); - width = Math.max(width, dr.mDrawableWidthBottom); - } - - if (mHint != null) { - int hintDes = -1; - int hintWidth; - if (mHintLayout != null && mEllipsize == null) { - hintDes = desired(mHintLayout, mUseBoundsForWidth); - } - - if (hintDes < 0) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, + if (des < 0) { + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(), - mHintBoring); - if (hintBoring != null) { - mHintBoring = hintBoring; + mBoring); + if (boring != null) { + mBoring = boring; } + } else { + fromexisting = true; } - if (hintBoring == null || hintBoring == UNKNOWN_BORING) { - if (hintDes < 0) { - hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, - mHint.length(), mTextPaint, mTextDir, widthLimit, + if (boring == null || boring == UNKNOWN_BORING) { + if (des < 0) { + des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, + mTransformed.length(), mTextPaint, mTextDir, widthLimit, mUseBoundsForWidth)); } - hintWidth = hintDes; + width = des; } else { - hintWidth = hintBoring.width; + if (mUseBoundsForWidth) { + RectF bbox = boring.getDrawingBoundingBox(); + float rightMax = Math.max(bbox.right, boring.width); + float leftMin = Math.min(bbox.left, 0); + width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin)); + } else { + width = boring.width; + } } - if (hintWidth > width) { - width = hintWidth; + final Drawables dr = mDrawables; + if (dr != null) { + width = Math.max(width, dr.mDrawableWidthTop); + width = Math.max(width, dr.mDrawableWidthBottom); } - } - width += getCompoundPaddingLeft() + getCompoundPaddingRight(); + if (mHint != null) { + int hintDes = -1; + int hintWidth; - if (mMaxWidthMode == EMS) { - width = Math.min(width, mMaxWidth * getLineHeight()); - } else { - width = Math.min(width, mMaxWidth); - } + if (mHintLayout != null && mEllipsize == null) { + hintDes = desired(mHintLayout, mUseBoundsForWidth); + } - if (mMinWidthMode == EMS) { - width = Math.max(width, mMinWidth * getLineHeight()); - } else { - width = Math.max(width, mMinWidth); - } + if (hintDes < 0) { + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), + getResolvedMinimumFontMetrics(), + mHintBoring); + if (hintBoring != null) { + mHintBoring = hintBoring; + } + } - // Check against our minimum width - width = Math.max(width, getSuggestedMinimumWidth()); + if (hintBoring == null || hintBoring == UNKNOWN_BORING) { + if (hintDes < 0) { + hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, + mHint.length(), mTextPaint, mTextDir, widthLimit, + mUseBoundsForWidth)); + } + hintWidth = hintDes; + } else { + hintWidth = hintBoring.width; + } - if (widthMode == MeasureSpec.AT_MOST) { - width = Math.min(widthSize, width); - } - } + if (hintWidth > width) { + width = hintWidth; + } + } - int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); - int unpaddedWidth = want; + width += getCompoundPaddingLeft() + getCompoundPaddingRight(); - if (mHorizontallyScrolling) want = VERY_WIDE; + if (mMaxWidthMode == EMS) { + width = Math.min(width, mMaxWidth * getLineHeight()); + } else { + width = Math.min(width, mMaxWidth); + } - int hintWant = want; - int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); + if (mMinWidthMode == EMS) { + width = Math.max(width, mMinWidth * getLineHeight()); + } else { + width = Math.max(width, mMinWidth); + } - if (mLayout == null) { - makeNewLayout(want, hintWant, boring, hintBoring, - width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); - } else { - final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) - || (mLayout.getEllipsizedWidth() - != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); + // Check against our minimum width + width = Math.max(width, getSuggestedMinimumWidth()); + + if (widthMode == MeasureSpec.AT_MOST) { + width = Math.min(widthSize, width); + } + } - final boolean widthChanged = (mHint == null) && (mEllipsize == null) - && (want > mLayout.getWidth()) - && (mLayout instanceof BoringLayout - || (fromexisting && des >= 0 && des <= want)); + int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); + int unpaddedWidth = want; - final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); + if (mHorizontallyScrolling) want = VERY_WIDE; - if (layoutChanged || maximumChanged) { - if (!maximumChanged && widthChanged) { - mLayout.increaseWidthTo(want); + int hintWant = want; + int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); + + if (mLayout == null) { + makeNewLayout(want, hintWant, boring, hintBoring, + width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); + } else { + final boolean layoutChanged = + (mLayout.getWidth() != want) || (hintWidth != hintWant) + || (mLayout.getEllipsizedWidth() + != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); + + final boolean widthChanged = (mHint == null) && (mEllipsize == null) + && (want > mLayout.getWidth()) + && (mLayout instanceof BoringLayout + || (fromexisting && des >= 0 && des <= want)); + + final boolean maximumChanged = + (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); + + if (layoutChanged || maximumChanged) { + if (!maximumChanged && widthChanged) { + mLayout.increaseWidthTo(want); + } else { + makeNewLayout(want, hintWant, boring, hintBoring, + width - getCompoundPaddingLeft() - getCompoundPaddingRight(), + false); + } } else { - makeNewLayout(want, hintWant, boring, hintBoring, - width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); + // Nothing has changed } - } else { - // Nothing has changed } - } - if (heightMode == MeasureSpec.EXACTLY) { - // Parent has told us how big to be. So be it. - height = heightSize; - mDesiredHeightAtMeasure = -1; - } else { - int desired = getDesiredHeight(); + if (heightMode == MeasureSpec.EXACTLY) { + // Parent has told us how big to be. So be it. + height = heightSize; + mDesiredHeightAtMeasure = -1; + } else { + int desired = getDesiredHeight(); - height = desired; - mDesiredHeightAtMeasure = desired; + height = desired; + mDesiredHeightAtMeasure = desired; - if (heightMode == MeasureSpec.AT_MOST) { - height = Math.min(desired, heightSize); + if (heightMode == MeasureSpec.AT_MOST) { + height = Math.min(desired, heightSize); + } } - } - int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); - if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { - unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); - } + int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); + if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { + unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); + } - /* - * We didn't let makeNewLayout() register to bring the cursor into view, - * so do it here if there is any possibility that it is needed. - */ - if (mMovement != null - || mLayout.getWidth() > unpaddedWidth - || mLayout.getHeight() > unpaddedHeight) { - registerForPreDraw(); - } else { - scrollTo(0, 0); - } + /* + * We didn't let makeNewLayout() register to bring the cursor into view, + * so do it here if there is any possibility that it is needed. + */ + if (mMovement != null + || mLayout.getWidth() > unpaddedWidth + || mLayout.getHeight() > unpaddedHeight) { + registerForPreDraw(); + } else { + scrollTo(0, 0); + } - setMeasuredDimension(width, height); + setMeasuredDimension(width, height); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } /** diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 6b5a367ab4602c8affeb98421c148ac018c1ef88..7a01ad340c561be0b4d07c84716e4e0b1f0ab731 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -54,6 +54,7 @@ public enum DesktopModeFlags { Flags::enableDesktopWindowingWallpaperActivity, true), ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), + ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true), ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true), ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true), ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 0f2dd10d7f474cdfb788cda7c779a3fb5ca2c42a..2c21417fb79023bcf88b3a489f5bcf3739090466 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -49,6 +49,7 @@ import android.content.ComponentName; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; +import android.os.BinderProxy; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -1089,8 +1090,13 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { final StringBuilder sb = new StringBuilder(); - sb.append('{'); sb.append(mContainer); - sb.append(" m="); sb.append(modeToString(mMode)); + sb.append('{'); + if (mContainer != null && !(mContainer.asBinder() instanceof BinderProxy)) { + // Only log the token if it is not a binder proxy and has additional container info + sb.append(mContainer); + sb.append(" "); + } + sb.append("m="); sb.append(modeToString(mMode)); sb.append(" f="); sb.append(flagsToString(mFlags)); if (mParent != null) { sb.append(" p="); sb.append(mParent); diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 3fe63ab17248aec955a4dc200bc875dcaa266d9c..a88a1728348274e6d235c0cf2190d8dbb145d25f 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -1120,8 +1120,8 @@ public final class WindowContainerTransaction implements Parcelable { @NonNull public String toString() { return "WindowContainerTransaction {" - + " changes = " + mChanges - + " hops = " + mHierarchyOps + + " changes= " + mChanges + + " hops= " + mHierarchyOps + " errorCallbackToken=" + mErrorCallbackToken + " taskFragmentOrganizer=" + mTaskFragmentOrganizer + " }"; diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index d39ecabbb2d25bc46eb26c599a84e3b96a5d45d3..eebdeadcdeb295d64ff962cbf7282b8df5a36c61 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -352,6 +352,16 @@ flag { bug: "375992828" } +flag { + name: "enable_desktop_system_dialogs_transitions" + namespace: "lse_desktop_experience" + description: "Enables custom transitions for system dialogs in Desktop Mode." + bug: "335638193" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "enable_move_to_next_display_shortcut" namespace: "lse_desktop_experience" @@ -392,4 +402,11 @@ flag { namespace: "lse_desktop_experience" description: "Enables HSUM on desktop mode." bug: "366397912" +} + +flag { + name: "enable_multiple_desktops" + namespace: "lse_desktop_experience" + description: "Enable multiple desktop sessions for desktop windowing." + bug: "379158791" } \ No newline at end of file diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 392c307de7ba7ce5864b8c5261f4470a09ba10ba..96b9dc7cab0efec0a9ba215f1df53a7f3a804355 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -97,3 +97,12 @@ flag { is_fixed_read_only: true bug: "308662081" } + +flag { + name: "jank_api" + namespace: "window_surfaces" + description: "Adds the jank data listener to AttachedSurfaceControl" + is_fixed_read_only: true + is_exported: true + bug: "293949943" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 68e78fed29c5e1a2d0a817349da9622d6b1f8d2a..ff69610dbf0e56c16a9029b7e82ec1d7b8bcc07d 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -160,12 +160,21 @@ flag { } flag { - name: "delegate_unhandled_drags" - is_exported: true - namespace: "multitasking" - description: "Enables delegating unhandled drags to SystemUI" - bug: "320797628" - is_fixed_read_only: true + name: "delegate_unhandled_drags" + is_exported: true + namespace: "multitasking" + description: "Enables delegating unhandled drags to SystemUI" + bug: "320797628" + is_fixed_read_only: true +} + +flag { + name: "supports_drag_assistant_to_multiwindow" + is_exported: true + namespace: "multitasking" + description: "Enables support for dragging the assistant into multiwindow" + bug: "371206207" + is_fixed_read_only: true } flag { @@ -267,6 +276,16 @@ flag { } } +flag { + name: "system_ui_post_animation_end" + namespace: "windowing_frontend" + description: "Run AnimatorListener#onAnimationEnd on next frame for SystemUI" + bug: "300035126" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "system_ui_immersive_confirmation_dialog" namespace: "windowing_frontend" @@ -398,6 +417,17 @@ flag { bug: "362938401" } +flag { + name: "record_task_snapshots_before_shutdown" + namespace: "windowing_frontend" + description: "Record task snapshots before shutdown" + bug: "376821232" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "predictive_back_three_button_nav" namespace: "systemui" diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java index 56f633fbc6c9a9d044b7e5491f75719f363fb846..ca1fc0a6d41ff0e8478c6510a55d7047da9152ca 100644 --- a/core/java/com/android/internal/app/AppLocaleCollector.java +++ b/core/java/com/android/internal/app/AppLocaleCollector.java @@ -41,7 +41,7 @@ import java.util.Set; import java.util.stream.Collectors; /** The Locale data collector for per-app language. */ -public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { +public class AppLocaleCollector implements LocaleCollectorBase { private static final String TAG = AppLocaleCollector.class.getSimpleName(); private final Context mContext; private final String mAppPackageName; @@ -167,8 +167,8 @@ public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollecto } @Override - public HashSet getIgnoredLocaleList(boolean translatedOnly) { - HashSet langTagsToIgnore = new HashSet<>(); + public Set getIgnoredLocaleList(boolean translatedOnly) { + Set langTagsToIgnore = new HashSet<>(); if (mAppCurrentLocale != null) { langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag()); diff --git a/core/java/com/android/internal/app/LocaleCollectorBase.java b/core/java/com/android/internal/app/LocaleCollectorBase.java new file mode 100644 index 0000000000000000000000000000000000000000..f8390777107472601d2529863a5a0c2920072834 --- /dev/null +++ b/core/java/com/android/internal/app/LocaleCollectorBase.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * The interface which provides the locale list. + */ +public interface LocaleCollectorBase { + + /** Gets the ignored locale list. */ + Set getIgnoredLocaleList(boolean translatedOnly); + + /** Gets the supported locale list. */ + Set getSupportedLocaleList(LocaleStore.LocaleInfo parent, + boolean translatedOnly, boolean isForCountryMode); + + /** Indicates if the class work for specific package. */ + boolean hasSpecificPackageName(); +} diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index ef4acd1cdfcb0b1269c1b84bf7b96ec9aed9d99a..ffffefa64758779ffb92f5da0c9d945031fae02e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -44,7 +44,10 @@ import java.util.Set; *

    It shows suggestions at the top, then the rest of the locales. * Allows the user to search for locales using both their native name and their name in the * default locale.

    + * + * @deprecated use SettingLib's widget instead of customized UIs. */ +@Deprecated public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener { private static final String TAG = LocalePickerWithRegion.class.getSimpleName(); private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; @@ -78,21 +81,6 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {} } - /** - * The interface which provides the locale list. - */ - interface LocaleCollectorBase { - /** Gets the ignored locale list. */ - HashSet getIgnoredLocaleList(boolean translatedOnly); - - /** Gets the supported locale list. */ - Set getSupportedLocaleList(LocaleStore.LocaleInfo parent, - boolean translatedOnly, boolean isForCountryMode); - - /** Indicates if the class work for specific package. */ - boolean hasSpecificPackageName(); - } - private static LocalePickerWithRegion createNumberingSystemPicker( LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly, OnActionExpandListener onActionExpandListener, diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java index 416f510b3230d879a5cec4b321d723cc0a1cca9d..c7931cbfcc5d712e35ea246898593983f32d1e7e 100644 --- a/core/java/com/android/internal/app/SystemLocaleCollector.java +++ b/core/java/com/android/internal/app/SystemLocaleCollector.java @@ -24,7 +24,7 @@ import java.util.HashSet; import java.util.Set; /** The Locale data collector for System language. */ -class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase { +public class SystemLocaleCollector implements LocaleCollectorBase { private final Context mContext; private LocaleList mExplicitLocales; @@ -32,14 +32,14 @@ class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBas this(context, null); } - SystemLocaleCollector(Context context, LocaleList explicitLocales) { + public SystemLocaleCollector(Context context, LocaleList explicitLocales) { mContext = context; mExplicitLocales = explicitLocales; } @Override - public HashSet getIgnoredLocaleList(boolean translatedOnly) { - HashSet ignoreList = new HashSet<>(); + public Set getIgnoredLocaleList(boolean translatedOnly) { + Set ignoreList = new HashSet<>(); if (!translatedOnly) { final LocaleList userLocales = LocalePicker.getLocales(); final String[] langTags = userLocales.toLanguageTags().split(","); diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java index a21a84261ae01d3824f5e840ec4205d9720ef242..543adac14d364439a6c2dbae32d220f4d5bd430e 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -257,7 +257,6 @@ public final class AssociationState { if (VALIDATE_TIMES) { if (mActiveDuration > mAssociationState.mTotalActiveDuration) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); Slog.w(TAG, "Source act duration " + mActiveDurations + " exceeds total " + mAssociationState.mTotalActiveDuration + " in procstate " + mActiveProcState + " in source " @@ -650,7 +649,6 @@ public final class AssociationState { + mySrc.mKey.mProcess + " to assoc " + mName); if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+" + otherSrc.mDuration + (newSrc ? " (new)" : " (old)") + " exceeds total " @@ -665,7 +663,6 @@ public final class AssociationState { + mySrc.mKey.mProcess + " to assoc " + mName); if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+" + otherSrc.mActiveDuration + (newSrc ? " (new)" : " (old)") + " exceeds total " @@ -746,14 +743,12 @@ public final class AssociationState { if (VALIDATE_TIMES) { if (src.mDuration > mTotalDuration) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); Slog.w(TAG, "Reading tot duration " + src.mDuration + " exceeds total " + mTotalDuration + " in source " + src.mKey.mProcess + " to assoc " + mName, ex); } if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) { RuntimeException ex = new RuntimeException(); - ex.fillInStackTrace(); Slog.w(TAG, "Reading act duration " + src.mActiveDuration + " exceeds total " + mTotalDuration + " in source " + src.mKey.mProcess + " to assoc " + mName, ex); diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 0dbdb36977f441f06da6c3c248d613e087f7cdd3..7523a2d24af85382c18da5f16db007010aa3c213 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -538,7 +538,6 @@ public final class ProcessState { public void incActiveServices(String serviceName) { if (DEBUG && "".equals(mName)) { RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Slog.d(TAG, "incActiveServices: " + this + " service=" + serviceName + " to " + (mNumActiveServices+1), here); } @@ -551,7 +550,6 @@ public final class ProcessState { public void decActiveServices(String serviceName) { if (DEBUG && "".equals(mName)) { RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName + " to " + (mNumActiveServices-1), here); } @@ -569,7 +567,6 @@ public final class ProcessState { public void incStartedServices(int memFactor, long now, String serviceName) { if (false) { RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName + " to " + (mNumStartedServices+1), here); } @@ -585,7 +582,6 @@ public final class ProcessState { public void decStartedServices(int memFactor, long now, String serviceName) { if (false) { RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName + " to " + (mNumStartedServices-1), here); } diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 21c7baab4e8340d3c4c0d96e58492faff3bcd723..469ab48385da9a6e17c033946f6c199b2b01f27a 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -410,4 +410,15 @@ oneway interface IBackupTransport { * however backups initiated by the framework will call this method to retrieve one. */ void getBackupManagerMonitor(in AndroidFuture resultFuture); + + /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. The + * {@code resultFuture} should be completed with a subset of the packages passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + * + * @param operationType 0 for backup, 1 for restore. + */ + void getPackagesThatShouldNotUseRestrictedMode(in List packageNames, int operationType, + in AndroidFuture> resultFuture); } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 44c0bd01d54530b7197d7697a35c7fec5d201f95..2834e68833160ebc90a9f6203d5a90a67250088e 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -139,7 +139,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai } static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) { - return new JankInfo(jankStat.frameVsyncId).update(jankStat); + return new JankInfo(jankStat.getVsyncId()).update(jankStat); } private JankInfo(long frameVsyncId) { @@ -154,10 +154,10 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai private JankInfo update(SurfaceControl.JankData jankStat) { this.surfaceControlCallbackFired = true; - this.jankType = jankStat.jankType; - this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs); + this.jankType = jankStat.getJankType(); + this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.getFrameIntervalNanos()); if (Flags.useSfFrameDuration()) { - this.totalDurationNanos = jankStat.actualAppFrameTimeNs; + this.totalDurationNanos = jankStat.getActualAppFrameTimeNanos(); } return this; } @@ -458,14 +458,14 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai } for (SurfaceControl.JankData jankStat : jankData) { - if (!isInRange(jankStat.frameVsyncId)) { + if (!isInRange(jankStat.getVsyncId())) { continue; } - JankInfo info = findJankInfo(jankStat.frameVsyncId); + JankInfo info = findJankInfo(jankStat.getVsyncId()); if (info != null) { info.update(jankStat); } else { - mJankInfos.put((int) jankStat.frameVsyncId, + mJankInfos.put((int) jankStat.getVsyncId(), JankInfo.createFromSurfaceControlCallback(jankStat)); } } diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 6b6b81f1f805018c2d96b4573c877f3d1cfa6953..48d0d6c777debcf4c906ca3851af91f01af7deff 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -410,6 +410,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, private int mLocaleConfigRes; private boolean mAllowCrossUidActivitySwitchFromBelow; + @Nullable + private int[] mAlternateLauncherIconResIds; + @Nullable + private int[] mAlternateLauncherLabelResIds; + private List mSplits; @NonNull @@ -874,6 +879,18 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, return adoptPermissions; } + @Nullable + @Override + public int[] getAlternateLauncherIconResIds() { + return mAlternateLauncherIconResIds; + } + + @Nullable + @Override + public int[] getAlternateLauncherLabelResIds() { + return mAlternateLauncherLabelResIds; + } + @NonNull @Override public List getApexSystemServices() { @@ -1887,6 +1904,19 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, return setBoolean(Booleans.ALLOW_NATIVE_HEAP_POINTER_TAGGING, value); } + @Override + public PackageImpl setAlternateLauncherIconResIds(@Nullable int[] alternateLauncherIconResIds) { + this.mAlternateLauncherIconResIds = alternateLauncherIconResIds; + return this; + } + + @Override + public PackageImpl setAlternateLauncherLabelResIds( + @Nullable int[] alternateLauncherLabelResIds) { + this.mAlternateLauncherLabelResIds = alternateLauncherLabelResIds; + return this; + } + @Override public PackageImpl setTaskReparentingAllowed(boolean value) { return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value); @@ -3273,6 +3303,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeLong(this.mBooleans2); dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow); dest.writeInt(this.mIntentMatchingFlags); + dest.writeIntArray(this.mAlternateLauncherIconResIds); + dest.writeIntArray(this.mAlternateLauncherLabelResIds); } private void writeFeatureFlagState(@NonNull Parcel dest) { @@ -3465,6 +3497,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.mBooleans2 = in.readLong(); this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean(); this.mIntentMatchingFlags = in.readInt(); + this.mAlternateLauncherIconResIds = in.createIntArray(); + this.mAlternateLauncherLabelResIds = in.createIntArray(); assignDerivedFields(); assignDerivedFields2(); diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index 70b7953ed364ff807ec968d8f2153d1af3009d2c..c953d88c94828a690a803de8d9a49884bb26a4d7 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -214,13 +214,30 @@ public class AconfigFlags { * @param parser XML parser object currently parsing an element * @return true if the element is disabled because of its feature flag */ + public boolean skipCurrentElement(@Nullable ParsingPackage pkg, @NonNull XmlPullParser parser) { + return skipCurrentElement(pkg, parser, /* allowNoNamespace= */ false); + } + + /** + * Check if the element in {@code parser} should be skipped because of the feature flag. + * @param pkg The package being parsed + * @param parser XML parser object currently parsing an element + * @param allowNoNamespace Whether to allow namespace null + * @return true if the element is disabled because of its feature flag + */ public boolean skipCurrentElement( - @NonNull ParsingPackage pkg, - @NonNull XmlResourceParser parser) { + @Nullable ParsingPackage pkg, + @NonNull XmlPullParser parser, + boolean allowNoNamespace + ) { if (!Flags.manifestFlagging()) { return false; } String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag"); + // If allow no namespace, make another attempt to parse feature flag with null namespace. + if (featureFlag == null && allowNoNamespace) { + featureFlag = parser.getAttributeValue(null, "featureFlag"); + } if (featureFlag == null) { return false; } @@ -242,7 +259,7 @@ public class AconfigFlags { + " behind feature flag " + featureFlag + " = " + flagValue); shouldSkip = true; } - if (android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) { + if (pkg != null && android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) { pkg.addFeatureFlag(featureFlag, flagValue); } return shouldSkip; diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index f4bceb880617d613e68cf31ef8a6269ba5d60914..67b985a614553f95f16741484dc141cbff618a7a 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -413,6 +413,18 @@ public interface ParsingPackage { ParsingPackage setOnBackInvokedCallbackEnabled(boolean enableOnBackInvokedCallback); + /** + * Set the drawable resources id array of the alternate icons that are parsing from the + * AndroidManifest file + */ + ParsingPackage setAlternateLauncherIconResIds(int[] alternateLauncherIconResIds); + + /** + * Set the string resources id array of the alternate labels that are parsing from the + * AndroidManifest file + */ + ParsingPackage setAlternateLauncherLabelResIds(int[] alternateLauncherLabelResIds); + @CallSuper ParsedPackage hideAsParsed(); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 5db7b419765858e56378900c4256cbd2edf69c48..8a6e6be1abbf45ce5488dc546e876a1f751411cf 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -46,6 +46,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; +import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; @@ -154,6 +155,13 @@ public class ParsingPackageUtils { private static final String TAG = ParsingUtils.TAG; + // It is the maximum length of the typedArray of {@link android.R.attr#alternateIcons} + // and {@link android.R.attr#alternateLabels}. + private static final int MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH = 500; + + private static final String TYPE_STRING = "string"; + private static final String TYPE_DRAWABLE = "drawable"; + public static final boolean DEBUG_JAR = false; public static final boolean DEBUG_BACKUP = false; public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; @@ -2021,6 +2029,24 @@ public class ParsingPackageUtils { pkg.setManageSpaceActivityName(manageSpaceActivityName); } + if (Flags.changeLauncherBadging()) { + ParseResult result = drawableResIdArray(input, sa, res, + R.styleable.AndroidManifestApplication_alternateLauncherIcons, + MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH); + if (result.isError()) { + return input.error(result); + } + pkg.setAlternateLauncherIconResIds(result.getResult()); + + result = stringResIdArray(input, sa, res, + R.styleable.AndroidManifestApplication_alternateLauncherLabels, + MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH); + if (result.isError()) { + return input.error(result); + } + pkg.setAlternateLauncherLabelResIds(result.getResult()); + } + if (pkg.isBackupAllowed()) { // backupAgent, killAfterRestore, fullBackupContent, backupInForeground, // and restoreAnyVersion are only relevant if backup is possible for the @@ -3395,6 +3421,95 @@ public class ParsingPackageUtils { return sa.getResourceId(attribute, 0); } + /** + * Parse the drawable resource id array in the typed array {@code resourceId} + * if available. If {@code maxSize} is not zero, only parse and preserve at most + * {@code maxSize} ids. + */ + private static ParseResult drawableResIdArray(ParseInput input, @NonNull TypedArray sa, + @NonNull Resources res, int resourceId, int maxSize) { + return resIdArray(input, sa, res, resourceId, TYPE_DRAWABLE, maxSize); + } + + /** + * Parse the string resource id array in the typed array {@code resourceId} + * if available. If {@code maxSize} is not zero, only parse and preserve at most + * {@code maxSize} ids. + */ + private static ParseResult stringResIdArray(ParseInput input, @NonNull TypedArray sa, + @NonNull Resources res, int resourceId, int maxSize) { + return resIdArray(input, sa, res, resourceId, TYPE_STRING, maxSize); + } + + /** + * Parse the resource id array in the typed array {@code resourceId} + * if available. If {@code maxSize} is larger than zero, only parse and preserve + * at most {@code maxSize} ids that type is matched to the {@code expectedTypeName}. + * Because the TypedArray allows mixed types in an array, if {@code expectedTypeName} + * is null, it means don't check the type. + */ + private static ParseResult resIdArray(ParseInput input, @NonNull TypedArray sa, + @NonNull Resources res, int resourceId, @Nullable String expectedTypeName, + int maxSize) { + if (!sa.hasValue(resourceId)) { + return input.success(null); + } + + final int typeArrayResId = sa.getResourceId(resourceId, /* defValue= */ 0); + if (typeArrayResId == 0) { + return input.success(null); + } + + // Parse the typedArray + try (TypedArray typedArray = res.obtainTypedArray(typeArrayResId)) { + final String typedArrayName = res.getResourceName(typeArrayResId); + final int length = typedArray.length(); + if (maxSize > 0 && length > maxSize) { + return input.error(TextUtils.formatSimple( + "The length of the typedArray (%s) is larger than %d.", + typedArrayName, maxSize)); + } + Set resourceIdSet = new ArraySet<>(); + for (int i = 0; i < length; i++) { + final int id = typedArray.getResourceId(i, /* defValue= */ 0); + // Add the id when the conditions are all matched: + // 1. The resource Id is not 0 + // 2. The type is the expected type + // 3. The id is not duplicated + if (id == 0) { + return input.error(TextUtils.formatSimple( + "There is an item that is not a resource id in the typedArray (%s).", + typedArrayName)); + } + + try { + if (resourceIdSet.contains(id)) { + return input.error(TextUtils.formatSimple( + "There is a duplicated resource (%s) in the typedArray (%s).", + res.getResourceName(id), typedArrayName)); + } + final String typeName = res.getResourceTypeName(id); + if (expectedTypeName != null + && !TextUtils.equals(typeName, expectedTypeName)) { + return input.error(TextUtils.formatSimple( + "There is a resource (%s) in the typedArray (%s) that is not a" + + " %s type.", res.getResourceName(id), typedArrayName, + expectedTypeName)); + } + } catch (Resources.NotFoundException e) { + return input.error(TextUtils.formatSimple( + "There is a resource in the typedArray (%s) that is not found in" + + " the app resources.", typedArrayName)); + } + resourceIdSet.add(id); + } + if (resourceIdSet.isEmpty()) { + return input.success(null); + } + return input.success(resourceIdSet.stream().mapToInt(i -> i).toArray()); + } + } + private static String string(@StyleableRes int attribute, TypedArray sa) { return sa.getString(attribute); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index bd746d5ecf048dcb33f325058c2a006bc9c9460a..270cf085b06ffdbec594ccbd5d445d5fede35d7c 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -46,9 +46,13 @@ import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.app.WindowConfiguration; +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; @@ -283,6 +287,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private final WearGestureInterceptionDetector mWearGestureInterceptionDetector; + @Nullable + private AppJankStatsCallback mAppJankStatsCallback; + DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { super(context); @@ -2336,6 +2343,38 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + public interface AppJankStatsCallback { + /** + * Called when app jank stats are being reported to the platform or when a widget needs + * to obtain a reference to the JankTracker instance to update states. + */ + JankTracker getAppJankTracker(); + } + + public void setAppJankStatsCallback(AppJankStatsCallback + jankStatsReportedCallback) { + mAppJankStatsCallback = jankStatsReportedCallback; + } + + @Override + @FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void reportAppJankStats(@NonNull AppJankStats appJankStats) { + if (mAppJankStatsCallback != null) { + JankTracker jankTracker = mAppJankStatsCallback.getAppJankTracker(); + if (jankTracker != null) { + jankTracker.mergeAppJankStats(appJankStats); + } + } + } + + @Override + public @Nullable JankTracker getJankTracker() { + if (mAppJankStatsCallback != null) { + return mAppJankStatsCallback.getAppJankTracker(); + } + return null; + } + @Override public String toString() { return super.toString() + "[" + getTitleSuffix(mWindow.getAttributes()) + "]"; diff --git a/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl new file mode 100644 index 0000000000000000000000000000000000000000..cc626f699d430f7c3b11e14e187ab6a26424d253 --- /dev/null +++ b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +oneway interface IDeviceLockedStateListener { + void onDeviceLockedStateChanged(boolean isDeviceLocked); +} \ No newline at end of file diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index a1c987f79304ca77f19d86fcd4ff7ca141f41d32..eb682dff14de7ddfe4d5566086efe43c806530ac 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -676,15 +676,30 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen return internMap.get(string); } + protected boolean validateGroups(ILogger logger, String[] groups) { + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + IProtoLogGroup g = mLogGroups.get(group); + if (g == null) { + logger.log("No IProtoLogGroup named " + group); + return false; + } + } + return true; + } + private int setTextLogging(boolean value, ILogger logger, String... groups) { + if (!validateGroups(logger, groups)) { + return -1; + } + for (int i = 0; i < groups.length; i++) { String group = groups[i]; IProtoLogGroup g = mLogGroups.get(group); if (g != null) { g.setLogToLogcat(value); } else { - logger.log("No IProtoLogGroup named " + group); - return -1; + throw new RuntimeException("No IProtoLogGroup named " + group); } } diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java index 70d148a311f69c5adfac80d878b8c5901083477d..967a5ed1744de1b2203118b76057fd5c25b742b0 100644 --- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java @@ -113,6 +113,10 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl { */ @Override public int startLoggingToLogcat(String[] groups, @NonNull ILogger logger) { + if (!validateGroups(logger, groups)) { + return -1; + } + mViewerConfigReader.loadViewerConfig(groups, logger); return super.startLoggingToLogcat(groups, logger); } @@ -125,8 +129,19 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl { */ @Override public int stopLoggingToLogcat(String[] groups, @NonNull ILogger logger) { + if (!validateGroups(logger, groups)) { + return -1; + } + + var status = super.stopLoggingToLogcat(groups, logger); + + if (status != 0) { + throw new RuntimeException("Failed to stop logging to logcat"); + } + + // If we successfully disabled logging, unload the viewer config. mViewerConfigReader.unloadViewerConfig(groups, logger); - return super.stopLoggingToLogcat(groups, logger); + return status; } @Deprecated diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index 3303d875c4279d5987ee3463060ad827c5f07bb9..8df3f2abcafd506e6b843bcdf0d6ac71fc6a29c1 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -88,15 +88,19 @@ public final class RavenwoodEnvironment { /** @hide */ public static class CompatIdsForTest { // Enabled by default + /** Used for testing */ @ChangeId public static final long TEST_COMPAT_ID_1 = 368131859L; + /** Used for testing */ @Disabled @ChangeId public static final long TEST_COMPAT_ID_2 = 368131701L; + /** Used for testing */ @EnabledAfter(targetSdkVersion = S) @ChangeId public static final long TEST_COMPAT_ID_3 = 368131659L; + /** Used for testing */ @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE) @ChangeId public static final long TEST_COMPAT_ID_4 = 368132057L; } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 0e85e046e1b68691de6bc21430600aaf81a09891..bf8a56508f54e310b52a84d4a086cb0620ad2c5e 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -20,6 +20,7 @@ import android.telephony.BarringInfo; import android.telephony.CallState; import android.telephony.CellIdentity; import android.telephony.CellInfo; +import android.telephony.CellularIdentifierDisclosure; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.LinkCapacityEstimate; import android.telephony.TelephonyDisplayInfo; @@ -28,6 +29,7 @@ import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.satellite.NtnSignalStrength; +import android.telephony.SecurityAlgorithmUpdate; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; @@ -87,4 +89,6 @@ oneway interface IPhoneStateListener { void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible); void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices); void onCarrierRoamingNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); + void onSecurityAlgorithmsChanged(in SecurityAlgorithmUpdate update); + void onCellularIdentifierDisclosedChanged(in CellularIdentifierDisclosure disclosure); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 0f268d5de62bab58a5d00458d2c5ae8b9ab3944a..a296fbd1cfe4f00f5b6283369884d5902ca50ae6 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -23,6 +23,7 @@ import android.telephony.BarringInfo; import android.telephony.CallQuality; import android.telephony.CellIdentity; import android.telephony.CellInfo; +import android.telephony.CellularIdentifierDisclosure; import android.telephony.LinkCapacityEstimate; import android.telephony.TelephonyDisplayInfo; import android.telephony.ims.ImsReasonInfo; @@ -30,6 +31,7 @@ import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseDataConnectionState; import android.telephony.satellite.NtnSignalStrength; +import android.telephony.SecurityAlgorithmUpdate; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; @@ -132,4 +134,7 @@ interface ITelephonyRegistry { void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg); void notifySatelliteStateChanged(boolean isEnabled); + void notifySecurityAlgorithmsChanged(int phoneId, int subId, in SecurityAlgorithmUpdate update); + void notifyCellularIdentifierDisclosedChanged( + int phoneId, int subId, in CellularIdentifierDisclosure disclosure); } diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index f2b36c3b99814a6950c62164925ba71778e834fc..7a21275d611e96a604a81b2a18422d8b61769f7d 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -59,6 +59,8 @@ import java.util.TreeSet; public final class NotificationProgressBar extends ProgressBar { private static final String TAG = "NotificationProgressBar"; + private NotificationProgressDrawable mNotificationProgressDrawable; + private NotificationProgressModel mProgressModel; @Nullable @@ -94,6 +96,12 @@ public final class NotificationProgressBar extends ProgressBar { defStyleAttr, defStyleRes); + try { + mNotificationProgressDrawable = getNotificationProgressDrawable(); + } catch (IllegalStateException ex) { + Log.e(TAG, "Can't get NotificationProgressDrawable", ex); + } + // Supports setting the tracker in xml, but ProgressStyle notifications set/override it // via {@code setProgressTrackerIcon}. final Drawable tracker = a.getDrawable(R.styleable.NotificationProgressBar_tracker); @@ -131,11 +139,8 @@ public final class NotificationProgressBar extends ProgressBar { progressMax, mProgressModel.isStyledByProgress()); - try { - final NotificationProgressDrawable drawable = getNotificationProgressDrawable(); - drawable.setParts(mProgressDrawableParts); - } catch (IllegalStateException ex) { - Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex); + if (mNotificationProgressDrawable != null) { + mNotificationProgressDrawable.setParts(mProgressDrawableParts); } setMax(progressMax); @@ -195,10 +200,6 @@ public final class NotificationProgressBar extends ProgressBar { } private void setTracker(@Nullable Drawable tracker) { - if (isIndeterminate() && tracker != null) { - return; - } - final boolean needUpdate = mTracker != null && tracker != mTracker; if (needUpdate) { mTracker.setCallback(null); @@ -222,6 +223,9 @@ public final class NotificationProgressBar extends ProgressBar { } mTracker = tracker; + if (mNotificationProgressDrawable != null) { + mNotificationProgressDrawable.setHasTrackerIcon(mTracker != null); + } configureTrackerBounds(); @@ -274,16 +278,6 @@ public final class NotificationProgressBar extends ProgressBar { mTrackerDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); } - @Override - @RemotableViewMethod - public synchronized void setIndeterminate(boolean indeterminate) { - super.setIndeterminate(indeterminate); - - if (isIndeterminate()) { - setTracker(null); - } - } - @Override protected boolean verifyDrawable(@NonNull Drawable who) { return who == mTracker || super.verifyDrawable(who); @@ -421,6 +415,8 @@ public final class NotificationProgressBar extends ProgressBar { @Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); + + if (isIndeterminate()) return; drawTracker(canvas); } diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index fb6937c94a3e1867f263b4cd9036f6d887d627cb..e95225eede99cc69b3f3e5e6828075cd8ac0a65e 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -23,7 +23,6 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -62,21 +61,15 @@ public final class NotificationProgressDrawable extends Drawable { private boolean mMutated; private final ArrayList mParts = new ArrayList<>(); + private boolean mHasTrackerIcon; private final RectF mSegRectF = new RectF(); private final Rect mPointRect = new Rect(); private final RectF mPointRectF = new RectF(); - private final Paint mStrokePaint = new Paint(); - private final Paint mDashedStrokePaint = new Paint(); private final Paint mFillPaint = new Paint(); { - mStrokePaint.setStyle(Paint.Style.STROKE); - mStrokePaint.setStrokeCap(Paint.Cap.ROUND); - - mDashedStrokePaint.setStyle(Paint.Style.STROKE); - mFillPaint.setStyle(Paint.Style.FILL); } @@ -87,49 +80,15 @@ public final class NotificationProgressDrawable extends Drawable { } /** - *

    Set the stroke width and default color for the drawable.

    - *

    Note: changing this property will affect all instances of a drawable loaded from a - * resource. It is recommended to invoke - * {@link #mutate()} before changing this property.

    - * - * @param width The width in pixels of the stroke - * @param color The color of the stroke - * @see #mutate() - * @see #setStroke(int, int, float, float) - */ - public void setStroke(int width, @ColorInt int color) { - setStroke(width, color, 0, 0); - } - - /** - *

    Set the stroke width and default color for the drawable. This method can also be used - * to dash the stroke for the dashed segments.

    - *

    Note: changing this property will affect all instances of a drawable loaded from a - * resource. It is recommended to invoke {@link #mutate()} before changing this property.

    - * - * @param width The width in pixels of the stroke - * @param color The color of the stroke - * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes - * @param dashGap The gap in pixels between dashes - * @see #mutate() - * @see #setStroke(int, int) - */ - public void setStroke(int width, @ColorInt int color, float dashWidth, float dashGap) { - mState.setStroke(width, color, dashWidth, dashGap); - setStrokeInternal(width, dashWidth, dashGap); - } - - /** - *

    Set the stroke default color for the drawable.

    + *

    Set the segment default color for the drawable.

    *

    Note: changing this property will affect all instances of a drawable loaded from a * resource. It is recommended to invoke {@link #mutate()} before changing this property.

    * * @param color The color of the stroke * @see #mutate() - * @see #setStroke(int, int, float, float) */ - public void setStrokeDefaultColor(@ColorInt int color) { - mState.setStrokeColor(color); + public void setSegmentDefaultColor(@ColorInt int color) { + mState.setSegmentColor(color); } /** @@ -144,25 +103,14 @@ public final class NotificationProgressDrawable extends Drawable { mState.setPointRectColor(color); } - private void setStrokeInternal(int width, float dashWidth, float dashGap) { - mStrokePaint.setStrokeWidth(width); - - mDashedStrokePaint.setStrokeWidth(width); - DashPathEffect e = null; - if (dashWidth > 0) { - e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); - } - mDashedStrokePaint.setPathEffect(e); - - invalidateSelf(); - } - /** * Set the segments and points that constitute the drawable. */ public void setParts(List parts) { mParts.clear(); mParts.addAll(parts); + + invalidateSelf(); } /** @@ -172,6 +120,16 @@ public final class NotificationProgressDrawable extends Drawable { setParts(Arrays.asList(parts)); } + /** + * Set whether a tracker is drawn on top of this NotificationProgressDrawable. + */ + public void setHasTrackerIcon(boolean hasTrackerIcon) { + if (mHasTrackerIcon != hasTrackerIcon) { + mHasTrackerIcon = hasTrackerIcon; + invalidateSelf(); + } + } + @Override public void draw(@NonNull Canvas canvas) { final float pointRadius = @@ -181,6 +139,7 @@ public final class NotificationProgressDrawable extends Drawable { float x = (float) getBounds().left; final float centerY = (float) getBounds().centerY(); final float totalWidth = (float) getBounds().width(); + float segPointGap = mState.mSegPointGap; final int numParts = mParts.size(); for (int iPart = 0; iPart < numParts; iPart++) { @@ -188,15 +147,19 @@ public final class NotificationProgressDrawable extends Drawable { final Part prevPart = iPart == 0 ? null : mParts.get(iPart - 1); final Part nextPart = iPart + 1 == numParts ? null : mParts.get(iPart + 1); if (part instanceof Segment segment) { + // Update the segment-point gap to 2X upon seeing the first faded segment. + // (Assuming that all segments before are solid, and all segments after are faded.) + if (segment.mFaded) { + segPointGap = mState.mSegPointGap * 2; + } final float segWidth = segment.mFraction * totalWidth; // Advance the start position to account for a point immediately prior. - final float startOffset = getSegStartOffset(prevPart, pointRadius, - mState.mSegPointGap, x); + final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x); final float start = x + startOffset; // Retract the end position to account for the padding and a point immediately // after. - final float endOffset = getSegEndOffset(nextPart, pointRadius, mState.mSegPointGap, - mState.mSegSegGap, x + segWidth, totalWidth); + final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap, + mState.mSegSegGap, x + segWidth, totalWidth, mHasTrackerIcon); final float end = x + segWidth - endOffset; // Advance the current position to account for the segment's fraction of the total @@ -206,35 +169,15 @@ public final class NotificationProgressDrawable extends Drawable { // No space left to draw the segment if (start > end) continue; - if (segment.mDashed) { - // No caps when the segment is dashed. - - mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor - : mState.mFadedStrokeColor); - canvas.drawLine(start, centerY, end, centerY, mDashedStrokePaint); - } else if (end - start < mState.mStrokeWidth) { - // Not enough segment length to draw the caps - - final float rad = (end - start) / 2F; - final float capWidth = mStrokePaint.getStrokeWidth() / 2F; - - mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor - : mState.mStrokeColor); - - mSegRectF.set(start, centerY - capWidth, end, centerY + capWidth); - canvas.drawRoundRect(mSegRectF, rad, rad, mFillPaint); - } else { - // Leave space for the rounded line cap which extends beyond start/end. - final float capWidth = mStrokePaint.getStrokeWidth() / 2F; + final float radiusY = segment.mFaded ? mState.mFadedSegmentHeight / 2F + : mState.mSegmentHeight / 2F; + final float cornerRadius = mState.mSegmentCornerRadius; - // Transparent is not allowed (and also is the default in the data), so use that - // as a sentinel to be replaced by default - mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor - : mState.mStrokeColor); + mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor + : (segment.mFaded ? mState.mFadedSegmentColor : mState.mSegmentColor)); - canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY, - mStrokePaint); - } + mSegRectF.set(start, centerY - radiusY, end, centerY + radiusY); + canvas.drawRoundRect(mSegRectF, cornerRadius, cornerRadius, mFillPaint); } else if (part instanceof Point point) { final float pointWidth = 2 * pointRadius; float start = x - pointRadius; @@ -275,10 +218,17 @@ public final class NotificationProgressDrawable extends Drawable { return pointOffset + pointRadius + segPointGap; } - private static float getSegEndOffset(Part nextPart, float pointRadius, float segPointGap, - float segSegGap, float endX, float totalWidth) { + private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius, + float segPointGap, + float segSegGap, float endX, float totalWidth, boolean hasTrackerIcon) { if (nextPart == null) return 0F; - if (!(nextPart instanceof Point)) return segSegGap; + if (nextPart instanceof Segment nextSeg) { + if (!seg.mFaded && nextSeg.mFaded) { + // @see Segment#mFaded + return hasTrackerIcon ? 0F : segSegGap * 4F; + } + return segSegGap; + } final float pointWidth = 2 * pointRadius; final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth) @@ -439,21 +389,17 @@ public final class NotificationProgressDrawable extends Drawable { // Extract the theme attributes, if any. state.mThemeAttrsSegments = a.extractThemeAttrs(); - final int width = a.getDimensionPixelSize( - R.styleable.NotificationProgressDrawableSegments_width, state.mStrokeWidth); - final float dashWidth = a.getDimension( - R.styleable.NotificationProgressDrawableSegments_dashWidth, state.mStrokeDashWidth); - + state.mSegmentHeight = a.getDimension( + R.styleable.NotificationProgressDrawableSegments_height, state.mSegmentHeight); + state.mFadedSegmentHeight = a.getDimension( + R.styleable.NotificationProgressDrawableSegments_fadedHeight, + state.mFadedSegmentHeight); + state.mSegmentCornerRadius = a.getDimension( + R.styleable.NotificationProgressDrawableSegments_cornerRadius, + state.mSegmentCornerRadius); final int color = a.getColor(R.styleable.NotificationProgressDrawableSegments_color, - state.mStrokeColor); - - if (dashWidth != 0.0f) { - final float dashGap = a.getDimension( - R.styleable.NotificationProgressDrawableSegments_dashGap, state.mStrokeDashGap); - setStroke(width, color, dashWidth, dashGap); - } else { - setStroke(width, color); - } + state.mSegmentColor); + setSegmentDefaultColor(color); } private void updatePointsFromTypedArray(TypedArray a) { @@ -532,11 +478,24 @@ public final class NotificationProgressDrawable extends Drawable { /** * A segment is a part of the progress bar with non-zero length. For example, it can * represent a portion in a navigation journey with certain traffic condition. + * */ public static final class Segment implements Part { private final float mFraction; @ColorInt private final int mColor; - private final boolean mDashed; + /** Whether the segment is faded or not. + *

    + *

    +         *     When mFaded is set to true, a combination of the following is done to the segment:
    +         *       1. The drawing color is mColor with opacity updated to 15%.
    +         *       2. The segment-point gap is 2X the segment-point gap for non-faded segments.
    +         *       3. The gap between faded and non-faded segments is:
    +         *          4X the segment-segment gap, when there is no tracker icon
    +         *          0, when there is tracker icon
    +         *     
    + *

    + */ + private final boolean mFaded; public Segment(float fraction) { this(fraction, Color.TRANSPARENT); @@ -546,10 +505,10 @@ public final class NotificationProgressDrawable extends Drawable { this(fraction, color, false); } - public Segment(float fraction, @ColorInt int color, boolean dashed) { + public Segment(float fraction, @ColorInt int color, boolean faded) { mFraction = fraction; mColor = color; - mDashed = dashed; + mFaded = faded; } public float getFraction() { @@ -560,14 +519,14 @@ public final class NotificationProgressDrawable extends Drawable { return this.mColor; } - public boolean getDashed() { - return this.mDashed; + public boolean getFaded() { + return this.mFaded; } @Override public String toString() { - return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", dashed=" - + this.mDashed + ')'; + return "Segment(fraction=" + this.mFraction + ", color=" + this.mColor + ", faded=" + + this.mFaded + ')'; } // Needed for unit tests @@ -580,12 +539,12 @@ public final class NotificationProgressDrawable extends Drawable { Segment that = (Segment) other; if (Float.compare(this.mFraction, that.mFraction) != 0) return false; if (this.mColor != that.mColor) return false; - return this.mDashed == that.mDashed; + return this.mFaded == that.mFaded; } @Override public int hashCode() { - return Objects.hash(mFraction, mColor, mDashed); + return Objects.hash(mFraction, mColor, mFaded); } } @@ -675,11 +634,11 @@ public final class NotificationProgressDrawable extends Drawable { int mChangingConfigurations; float mSegSegGap = 0.0f; float mSegPointGap = 0.0f; - int mStrokeWidth = 0; - int mStrokeColor; - int mFadedStrokeColor; - float mStrokeDashWidth = 0.0f; - float mStrokeDashGap = 0.0f; + float mSegmentHeight; + float mFadedSegmentHeight; + float mSegmentCornerRadius; + int mSegmentColor; + int mFadedSegmentColor; float mPointRadius; float mPointRectInset; float mPointRectCornerRadius; @@ -699,11 +658,11 @@ public final class NotificationProgressDrawable extends Drawable { mChangingConfigurations = orig.mChangingConfigurations; mSegSegGap = orig.mSegSegGap; mSegPointGap = orig.mSegPointGap; - mStrokeWidth = orig.mStrokeWidth; - mStrokeColor = orig.mStrokeColor; - mFadedStrokeColor = orig.mFadedStrokeColor; - mStrokeDashWidth = orig.mStrokeDashWidth; - mStrokeDashGap = orig.mStrokeDashGap; + mSegmentHeight = orig.mSegmentHeight; + mFadedSegmentHeight = orig.mFadedSegmentHeight; + mSegmentCornerRadius = orig.mSegmentCornerRadius; + mSegmentColor = orig.mSegmentColor; + mFadedSegmentColor = orig.mFadedSegmentColor; mPointRadius = orig.mPointRadius; mPointRectInset = orig.mPointRectInset; mPointRectCornerRadius = orig.mPointRectCornerRadius; @@ -721,17 +680,17 @@ public final class NotificationProgressDrawable extends Drawable { } private void applyDensityScaling(int sourceDensity, int targetDensity) { - if (mStrokeWidth > 0) { - mStrokeWidth = scaleFromDensity( - mStrokeWidth, sourceDensity, targetDensity, true); + if (mSegmentHeight > 0) { + mSegmentHeight = scaleFromDensity( + mSegmentHeight, sourceDensity, targetDensity); } - if (mStrokeDashWidth > 0) { - mStrokeDashWidth = scaleFromDensity( - mStrokeDashWidth, sourceDensity, targetDensity); + if (mFadedSegmentHeight > 0) { + mFadedSegmentHeight = scaleFromDensity( + mFadedSegmentHeight, sourceDensity, targetDensity); } - if (mStrokeDashGap > 0) { - mStrokeDashGap = scaleFromDensity( - mStrokeDashGap, sourceDensity, targetDensity); + if (mSegmentCornerRadius > 0) { + mSegmentCornerRadius = scaleFromDensity( + mSegmentCornerRadius, sourceDensity, targetDensity); } if (mPointRadius > 0) { mPointRadius = scaleFromDensity( @@ -788,17 +747,9 @@ public final class NotificationProgressDrawable extends Drawable { } } - public void setStroke(int width, int color, float dashWidth, float dashGap) { - mStrokeWidth = width; - mStrokeDashWidth = dashWidth; - mStrokeDashGap = dashGap; - - setStrokeColor(color); - } - - public void setStrokeColor(int color) { - mStrokeColor = color; - mFadedStrokeColor = getFadedColor(color); + public void setSegmentColor(int color) { + mSegmentColor = color; + mFadedSegmentColor = getFadedColor(color); } public void setPointRectColor(int color) { @@ -808,11 +759,14 @@ public final class NotificationProgressDrawable extends Drawable { } /** - * Get a color with an opacity that's 50% of the input color. + * Get a color with an opacity that's 25% of the input color. */ @ColorInt static int getFadedColor(@ColorInt int color) { - return Color.argb(Color.alpha(color) / 2, Color.red(color), Color.green(color), + return Color.argb( + (int) (Color.alpha(color) * 0.25f + 0.5f), + Color.red(color), + Color.green(color), Color.blue(color)); } @@ -836,15 +790,6 @@ public final class NotificationProgressDrawable extends Drawable { } private void updateLocalState() { - final State state = mState; - - mStrokePaint.setStrokeWidth(state.mStrokeWidth); - mDashedStrokePaint.setStrokeWidth(state.mStrokeWidth); - - if (state.mStrokeDashWidth != 0.0f) { - final DashPathEffect e = new DashPathEffect( - new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); - mDashedStrokePaint.setPathEffect(e); - } + // NO-OP } } diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 5fc61b00e331401256000dceb3f1177c1b70affe..c96e979138dd6f634c99a7a841ae5e24f7a75dfe 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -19,12 +19,7 @@ package com.android.internal.widget; import android.annotation.Nullable; import android.app.Flags; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.util.AttributeSet; @@ -41,7 +36,6 @@ import android.widget.RemoteViews; public class NotificationRowIconView extends CachingIconView { private NotificationIconProvider mIconProvider; - private boolean mApplyCircularCrop = false; private Drawable mAppIcon = null; // Padding, background and colors set on the view prior to being overridden when showing the app @@ -221,84 +215,6 @@ public class NotificationRowIconView extends CachingIconView { } } - @Nullable - @Override - Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { - final Drawable original = super.loadSizeRestrictedIcon(icon); - final Drawable result; - if (mApplyCircularCrop) { - result = makeCircularDrawable(original); - } else { - result = original; - } - - return result; - } - - /** - * Enables circle crop that makes given image circular - */ - @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync") - public void setApplyCircularCrop(boolean applyCircularCrop) { - mApplyCircularCrop = applyCircularCrop; - } - - /** - * Async version of {@link NotificationRowIconView#setApplyCircularCrop} - */ - public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) { - mApplyCircularCrop = applyCircularCrop; - return () -> { - }; - } - - @Nullable - private Drawable makeCircularDrawable(@Nullable Drawable original) { - if (original == null) { - return original; - } - - final Bitmap source = drawableToBitmap(original); - - int size = Math.min(source.getWidth(), source.getHeight()); - - Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false); - Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - - final Canvas canvas = new Canvas(result); - final Paint paint = new Paint(); - paint.setShader( - new BitmapShader(squared, BitmapShader.TileMode.CLAMP, - BitmapShader.TileMode.CLAMP)); - paint.setAntiAlias(true); - float radius = size / 2f; - canvas.drawCircle(radius, radius, radius, paint); - return new BitmapDrawable(getResources(), result); - } - - private static Bitmap drawableToBitmap(Drawable drawable) { - if (drawable instanceof BitmapDrawable bitmapDrawable) { - final Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getConfig() == Bitmap.Config.HARDWARE) { - return bitmap.copy(Bitmap.Config.ARGB_8888, false); - } else { - return bitmap; - } - } - - int width = drawable.getIntrinsicWidth(); - width = width > 0 ? width : 1; - int height = drawable.getIntrinsicHeight(); - height = height > 0 ? height : 1; - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - } - /** * A provider that allows this view to verify whether it should use the app icon instead of the * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 53500596f938a4df6de50f31dddb63ad8ebde6dc..d05f5e3950b41bc3b67e68f3872e55b1d7306754 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -90,6 +90,28 @@ import java.util.UUID; @Immutable public interface AndroidPackage { + /** + * An array containing the drawable resources that used for the launcher + * activity icons. + * + * @see R.attr#alternateLauncherIcons + * @hide + */ + @Immutable.Ignore + @Nullable + int[] getAlternateLauncherIconResIds(); + + /** + * An array containing the string resources that used for the launcher + * activity labels. + * + * @see R.attr#alternateLauncherLabels + * @hide + */ + @Immutable.Ignore + @Nullable + int[] getAlternateLauncherLabelResIds(); + /** * @see ApplicationInfo#className * @see R.styleable#AndroidManifestApplication_name diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 25412581303c848a831c1ae2e2ebd2e10662b0ed..a21bf9abdd7b19630beea732e7b3e5a43bbfef67 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -77,6 +77,7 @@ cc_library_shared_for_libandroid_runtime { "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", "android_text_AndroidCharacter.cpp", + "android_text_Hyphenator.cpp", "android_util_AssetManager.cpp", "android_util_EventLog.cpp", "android_util_Log.cpp", @@ -166,7 +167,6 @@ cc_library_shared_for_libandroid_runtime { "android_view_SurfaceSession.cpp", "android_view_TextureView.cpp", "android_view_TunnelModeEnabledListener.cpp", - "android_text_Hyphenator.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", "android_os_HidlMemory.cpp", diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 50252c11ffb1512ac44553ab3486bcf91c707a3e..42406147b2f0c6d6e8459447146da785167dbf16 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -538,7 +538,7 @@ static bool attributionSourceStateForJavaParcel(JNIEnv *env, jobject jClientAttr return false; } - if (!(useContextAttributionSource && flags::use_context_attribution_source())) { + if (!(useContextAttributionSource && flags::data_delivery_permission_checks())) { clientAttribution.uid = Camera::USE_CALLING_UID; clientAttribution.pid = Camera::USE_CALLING_PID; } diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp index bb4084e8f39e6bbff1e7d0ee6b5fe5e80b9a3521..f64dec8eb21518bc03b6e5f965aaec138db431c6 100644 --- a/core/jni/android_hardware_OverlayProperties.cpp +++ b/core/jni/android_hardware_OverlayProperties.cpp @@ -106,7 +106,7 @@ static jobjectArray android_hardware_OverlayProperties_getLutProperties(JNIEnv* jlong nativeObject) { gui::OverlayProperties* overlayProperties = reinterpret_cast(nativeObject); - if (overlayProperties->lutProperties.has_value()) { + if (!overlayProperties || !overlayProperties->lutProperties) { return NULL; } auto& lutProperties = overlayProperties->lutProperties.value(); diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index f9d00edce3fa7fb089a9ce1c1658098e6f841331..5a183925e38e6fbb55f9eafea6ba86864278ba64 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -584,14 +584,23 @@ static jboolean android_media_AudioRecord_setInputDevice( return lpRecorder->setInputDevice(device_id) == NO_ERROR; } -static jint android_media_AudioRecord_getRoutedDeviceId( - JNIEnv *env, jobject thiz) { - +static jintArray android_media_AudioRecord_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { sp lpRecorder = getAudioRecord(env, thiz); - if (lpRecorder == 0) { - return 0; + if (lpRecorder == NULL) { + return NULL; + } + DeviceIdVector deviceIds = lpRecorder->getRoutedDeviceIds(); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; + } + jint *values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast(deviceIds[i]); } - return (jint)lpRecorder->getRoutedDeviceId(); + env->ReleaseIntArrayElements(result, values, 0); + return result; } // Enable and Disable Callback methods are synchronized on the Java side @@ -821,8 +830,7 @@ static const JNINativeMethod gMethods[] = { // name, signature, funcPtr {"native_start", "(II)I", (void *)android_media_AudioRecord_start}, {"native_stop", "()V", (void *)android_media_AudioRecord_stop}, - {"native_setup", - "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, @@ -846,7 +854,7 @@ static const JNINativeMethod gMethods[] = { {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_AudioRecord_native_getMetrics}, {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", (void *)android_media_AudioRecord_getRoutedDeviceIds}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 8eaa7aa99a2da9553a989f6c1dfb64989e4a1f2a..3d9a19e129a8b85ba3084c5ec2d3fbc73751fc73 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -109,6 +109,7 @@ static struct { // Valid only if an AudioDevicePort jfieldID mType; jfieldID mAddress; + jfieldID mSpeakerLayoutChannelMask; // other fields unused by JNI } gAudioPortFields; @@ -1084,6 +1085,8 @@ static jint convertAudioPortConfigToNativeWithDevicePort(JNIEnv *env, strncpy(nAudioPortConfig->ext.device.address, nDeviceAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN - 1); env->ReleaseStringUTFChars(jDeviceAddress, nDeviceAddress); + nAudioPortConfig->ext.device.speaker_layout_channel_mask = outChannelMaskToNative( + env->GetIntField(jAudioDevicePort, gAudioPortFields.mSpeakerLayoutChannelMask)); env->DeleteLocalRef(jDeviceAddress); env->DeleteLocalRef(jAudioDevicePort); return jStatus; @@ -1541,10 +1544,12 @@ static jint convertAudioPortFromNative(JNIEnv *env, ScopedLocalRef *jAu .encapsulation_metadata_types)); ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type); ScopedLocalRef jAddress(env, env->NewStringUTF(nAudioPort->ext.device.address)); + int speakerLayoutChannelMask = outChannelMaskFromNative( + nAudioPort->active_config.ext.device.speaker_layout_channel_mask); jAudioPort->reset(env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, jHandle.get(), jDeviceName.get(), jAudioProfiles.get(), jGains.get(), nAudioPort->ext.device.type, jAddress.get(), - jEncapsulationModes.get(), + speakerLayoutChannelMask, jEncapsulationModes.get(), jEncapsulationMetadataTypes.get(), jAudioDescriptors.get())); } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { @@ -3705,14 +3710,15 @@ int register_android_media_AudioSystem(JNIEnv *env) gAudioDevicePortCstor = GetMethodIDOrDie(env, audioDevicePortClass, "", "(Landroid/media/AudioHandle;Ljava/lang/String;Ljava/util/List;" - "[Landroid/media/AudioGain;ILjava/lang/String;[I[I" + "[Landroid/media/AudioGain;ILjava/lang/String;I[I[I" "Ljava/util/List;)V"); // When access AudioPort as AudioDevicePort gAudioPortFields.mType = GetFieldIDOrDie(env, audioDevicePortClass, "mType", "I"); gAudioPortFields.mAddress = GetFieldIDOrDie(env, audioDevicePortClass, "mAddress", "Ljava/lang/String;"); - + gAudioPortFields.mSpeakerLayoutChannelMask = + GetFieldIDOrDie(env, audioDevicePortClass, "mSpeakerLayoutChannelMask", "I"); jclass audioMixPortClass = FindClassOrDie(env, "android/media/AudioMixPort"); gAudioMixPortClass = MakeGlobalRefOrDie(env, audioMixPortClass); gAudioMixPortCstor = diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 1557f9ed47a5cc1a3339b6736a13c76402bb7d4d..5d4d1ce20e5d9a8f5198d31666feae846573ed5f 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1190,15 +1190,23 @@ static jboolean android_media_AudioTrack_setOutputDevice( } return lpTrack->setOutputDevice(device_id) == NO_ERROR; } - -static jint android_media_AudioTrack_getRoutedDeviceId( - JNIEnv *env, jobject thiz) { - +static jintArray android_media_AudioTrack_getRoutedDeviceIds(JNIEnv *env, jobject thiz) { sp lpTrack = getAudioTrack(env, thiz); if (lpTrack == NULL) { - return 0; + return NULL; + } + DeviceIdVector deviceIds = lpTrack->getRoutedDeviceIds(); + jintArray result; + result = env->NewIntArray(deviceIds.size()); + if (result == NULL) { + return NULL; + } + jint *values = env->GetIntArrayElements(result, 0); + for (unsigned int i = 0; i < deviceIds.size(); i++) { + values[i++] = static_cast(deviceIds[i]); } - return (jint)lpTrack->getRoutedDeviceId(); + env->ReleaseIntArrayElements(result, values, 0); + return result; } static void android_media_AudioTrack_enableDeviceCallback( @@ -1503,7 +1511,7 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioTrack_setAuxEffectSendLevel}, {"native_attachAuxEffect", "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice}, - {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, + {"native_getRoutedDeviceIds", "()[I", (void *)android_media_AudioTrack_getRoutedDeviceIds}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp index a1a035110cafef64b750e26e5699f776361eb6b9..ccdf633c842a5dfd50663e7524154bfb17d5e679 100644 --- a/core/jni/android_media_DeviceCallback.cpp +++ b/core/jni/android_media_DeviceCallback.cpp @@ -61,21 +61,20 @@ JNIDeviceCallback::~JNIDeviceCallback() } void JNIDeviceCallback::onAudioDeviceUpdate(audio_io_handle_t audioIo, - audio_port_handle_t deviceId) -{ + const DeviceIdVector& deviceIds) { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (env == NULL) { return; } - ALOGV("%s audioIo %d deviceId %d", __FUNCTION__, audioIo, deviceId); - env->CallStaticVoidMethod(mClass, - mPostEventFromNative, - mObject, - AUDIO_NATIVE_EVENT_ROUTING_CHANGE, deviceId, 0, NULL); + ALOGV("%s audioIo %d deviceIds %s", __FUNCTION__, audioIo, toString(deviceIds).c_str()); + // Java should query the new device ids once it gets the event. + // TODO(b/378505346): Pass the deviceIds to Java to avoid race conditions. + env->CallStaticVoidMethod(mClass, mPostEventFromNative, mObject, + AUDIO_NATIVE_EVENT_ROUTING_CHANGE, 0 /*arg1*/, 0 /*arg2*/, + NULL /*obj*/); if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); env->ExceptionClear(); } } - diff --git a/core/jni/android_media_DeviceCallback.h b/core/jni/android_media_DeviceCallback.h index 7ae788eaf0582d953c415c1245ca8b8cdc4b7769..0c9ccc89ba1a6c6821eca4fdb79469f89ac7a5f0 100644 --- a/core/jni/android_media_DeviceCallback.h +++ b/core/jni/android_media_DeviceCallback.h @@ -31,8 +31,7 @@ public: JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, jmethodID postEventFromNative); ~JNIDeviceCallback(); - virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, - audio_port_handle_t deviceId); + virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, const DeviceIdVector& deviceIds); private: void sendEvent(int event); diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 933781c3e924f9021313427ecbd634b4c6017ebb..e45cbaf07876621220dea0be9b447c871e5cc28b 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -18,10 +18,17 @@ #include #include #include +#ifdef __ANDROID__ #include +#else +#include +#include +#endif #include #include +#ifdef __ANDROID__ #include +#endif #include #include @@ -30,7 +37,12 @@ namespace android { static std::string buildFileName(const std::string& locale) { +#ifdef __ANDROID__ constexpr char SYSTEM_HYPHENATOR_PREFIX[] = "/system/usr/hyphen-data/hyph-"; +#else + std::string hyphenPath = base::GetProperty("ro.hyphen.data.dir", "/system/usr/hyphen-data"); + std::string SYSTEM_HYPHENATOR_PREFIX = hyphenPath + "/hyph-"; +#endif constexpr char SYSTEM_HYPHENATOR_SUFFIX[] = ".hyb"; std::string lowerLocale; lowerLocale.reserve(locale.size()); @@ -51,11 +63,22 @@ static std::pair mmapPatternFile(const std::string& loca return std::make_pair(nullptr, 0); } +#ifdef __ANDROID__ void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */); close(fd); if (ptr == MAP_FAILED) { return std::make_pair(nullptr, 0); } +#else + std::unique_ptr patternFile = + base::MappedFile::FromFd(fd, 0, st.st_size, PROT_READ); + close(fd); + if (patternFile == nullptr) { + return std::make_pair(nullptr, 0); + } + auto* mappedPtr = new base::MappedFile(std::move(*patternFile)); + char* ptr = mappedPtr->data(); +#endif return std::make_pair(reinterpret_cast(ptr), st.st_size); } @@ -210,9 +233,13 @@ static void init() { addHyphenatorAlias("und-Taml", "ta"); // Tamil addHyphenatorAlias("und-Telu", "te"); // Telugu +#ifdef __ANDROID__ tracing_perfetto::traceBegin(ATRACE_TAG_VIEW, "CacheUnicodeExtensionSubtagsKeyMap"); +#endif cacheUnicodeExtensionSubtagsKeyMap(); +#ifdef __ANDROID__ tracing_perfetto::traceEnd(ATRACE_TAG_VIEW); // CacheUnicodeExtensionSubtagsKeyMap +#endif } static const JNINativeMethod gMethods[] = { diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 49191ee02ad6b3aaf9dfcbb010b66f39a4f5dbbe..7ef7829c6ba556bb2357424c7919e8035515d455 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -1008,6 +1009,8 @@ jboolean android_os_Process_parseProcLineArray(JNIEnv* env, jobject clazz, } } if ((mode&PROC_OUT_STRING) != 0 && di < NS) { + std::replace_if(buffer+start, buffer+end, + [](unsigned char c){ return !std::isprint(c); }, '?'); jstring str = env->NewStringUTF(buffer+start); env->SetObjectArrayElement(outStrings, di, str); } diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index f007cc5a23bd1cfbd8a4c011b4f980b73d5ac60d..a09c405de1cd9f78e1fa847454d4ca261db4f863 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -67,6 +67,7 @@ static struct { jfieldID preferredFrameTimelineIndex; jfieldID frameTimelinesLength; jfieldID frameTimelines; + jfieldID numberQueuedBuffers; } vsyncEventDataClassInfo; } gDisplayEventReceiverClassInfo; @@ -165,7 +166,8 @@ static jobject createJavaVsyncEventData(JNIEnv* env, VsyncEventData vsyncEventDa return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init, frameTimelineObjs.get(), vsyncEventData.preferredFrameTimelineIndex, - vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval); + vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval, + vsyncEventData.numberQueuedBuffers); } void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, @@ -188,6 +190,9 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla env->SetLongField(vsyncEventDataObj.get(), gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval, vsyncEventData.frameInterval); + env->SetIntField(vsyncEventDataObj.get(), + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers, + vsyncEventData.numberQueuedBuffers); ScopedLocalRef frameTimelinesObj(env, @@ -441,7 +446,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, "", "([Landroid/view/" - "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJ)V"); + "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJI)V"); gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex = GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, @@ -456,6 +461,9 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, "frameTimelines", "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "numberQueuedBuffers", "I"); return res; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index d3bf36e60345a6e646c5b35c80f195b9434dd585..593b982d4cf209ad53d984c7b6664522c681c70b 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -758,54 +758,64 @@ static void nativeSetLuts(JNIEnv* env, jclass clazz, jlong transactionObj, jlong auto transaction = reinterpret_cast(transactionObj); SurfaceControl* const ctrl = reinterpret_cast(nativeObject); - ScopedIntArrayRW joffsets(env, joffsetArray); - if (joffsets.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray"); - return; - } - ScopedIntArrayRW jdimensions(env, jdimensionArray); - if (jdimensions.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray"); - return; - } - ScopedIntArrayRW jsizes(env, jsizeArray); - if (jsizes.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray"); - return; - } - ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray); - if (jsamplingKeys.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray"); - return; - } + std::vector offsets; + std::vector dimensions; + std::vector sizes; + std::vector samplingKeys; + int32_t fd = -1; + + if (jdimensionArray) { + jsize numLuts = env->GetArrayLength(jdimensionArray); + ScopedIntArrayRW joffsets(env, joffsetArray); + if (joffsets.get() == nullptr) { + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray"); + return; + } + ScopedIntArrayRW jdimensions(env, jdimensionArray); + if (jdimensions.get() == nullptr) { + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray"); + return; + } + ScopedIntArrayRW jsizes(env, jsizeArray); + if (jsizes.get() == nullptr) { + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray"); + return; + } + ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray); + if (jsamplingKeys.get() == nullptr) { + jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray"); + return; + } - jsize numLuts = env->GetArrayLength(jdimensionArray); - std::vector offsets(joffsets.get(), joffsets.get() + numLuts); - std::vector dimensions(jdimensions.get(), jdimensions.get() + numLuts); - std::vector sizes(jsizes.get(), jsizes.get() + numLuts); - std::vector samplingKeys(jsamplingKeys.get(), jsamplingKeys.get() + numLuts); + if (numLuts > 0) { + offsets = std::vector(joffsets.get(), joffsets.get() + numLuts); + dimensions = std::vector(jdimensions.get(), jdimensions.get() + numLuts); + sizes = std::vector(jsizes.get(), jsizes.get() + numLuts); + samplingKeys = std::vector(jsamplingKeys.get(), jsamplingKeys.get() + numLuts); - ScopedFloatArrayRW jbuffers(env, jbufferArray); - if (jbuffers.get() == nullptr) { - jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray"); - return; - } + ScopedFloatArrayRW jbuffers(env, jbufferArray); + if (jbuffers.get() == nullptr) { + jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray"); + return; + } - // create the shared memory and copy jbuffers - size_t bufferSize = jbuffers.size() * sizeof(float); - int32_t fd = ashmem_create_region("lut_shread_mem", bufferSize); - if (fd < 0) { - jniThrowRuntimeException(env, "ashmem_create_region() failed"); - return; - } - void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (ptr == MAP_FAILED) { - jniThrowRuntimeException(env, "Failed to map the shared memory"); - return; + // create the shared memory and copy jbuffers + size_t bufferSize = jbuffers.size() * sizeof(float); + fd = ashmem_create_region("lut_shared_mem", bufferSize); + if (fd < 0) { + jniThrowRuntimeException(env, "ashmem_create_region() failed"); + return; + } + void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (ptr == MAP_FAILED) { + jniThrowRuntimeException(env, "Failed to map the shared memory"); + return; + } + memcpy(ptr, jbuffers.get(), bufferSize); + // unmap + munmap(ptr, bufferSize); + } } - memcpy(ptr, jbuffers.get(), bufferSize); - // unmap - munmap(ptr, bufferSize); transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys); } @@ -1332,8 +1342,9 @@ static void nativeSetDisplaySize(JNIEnv* env, jclass clazz, } } -static jobject convertDeviceProductInfoToJavaObject( - JNIEnv* env, const std::optional& info) { +static jobject convertDeviceProductInfoToJavaObject(JNIEnv* env, + const std::optional& info, + bool isInternal) { using ModelYear = android::DeviceProductInfo::ModelYear; using ManufactureYear = android::DeviceProductInfo::ManufactureYear; using ManufactureWeekAndYear = android::DeviceProductInfo::ManufactureWeekAndYear; @@ -1368,7 +1379,8 @@ static jobject convertDeviceProductInfoToJavaObject( // Section 8.7 - Physical Address of HDMI Specification Version 1.3a using android::hardware::display::IDeviceProductInfoConstants; if (info->relativeAddress.size() != 4) { - connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN; + connectionToSinkType = isInternal ? IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN + : IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN; } else if (info->relativeAddress[0] == 0) { connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN; } else if (info->relativeAddress[1] == 0) { @@ -1390,12 +1402,14 @@ static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jlong id) { jobject object = env->NewObject(gStaticDisplayInfoClassInfo.clazz, gStaticDisplayInfoClassInfo.ctor); - env->SetBooleanField(object, gStaticDisplayInfoClassInfo.isInternal, - info.connectionType == ui::DisplayConnectionType::Internal); + + const bool isInternal = info.connectionType == ui::DisplayConnectionType::Internal; + env->SetBooleanField(object, gStaticDisplayInfoClassInfo.isInternal, isInternal); env->SetFloatField(object, gStaticDisplayInfoClassInfo.density, info.density); env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure); env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo, - convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo)); + convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo, + isInternal)); env->SetIntField(object, gStaticDisplayInfoClassInfo.installOrientation, static_cast(info.installOrientation)); return object; @@ -2163,7 +2177,7 @@ static void nativeClearTrustedPresentationCallback(JNIEnv* env, jclass clazz, jl class JankDataListenerWrapper : public JankDataListener { public: - JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) { + JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) : mRemovedVsyncId(-1) { mOnJankDataListenerWeak = env->NewWeakGlobalRef(onJankDataListenerObject); env->GetJavaVM(&mVm); } @@ -2174,6 +2188,12 @@ public: } bool onJankDataAvailable(const std::vector& jankData) override { + // Don't invoke the listener if we've been force removed and got this + // out-of-order callback. + if (mRemovedVsyncId == 0) { + return false; + } + JNIEnv* env = getEnv(); jobject target = env->NewLocalRef(mOnJankDataListenerWeak); @@ -2181,8 +2201,8 @@ public: return false; } - jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(), - gJankDataClassInfo.clazz, nullptr); + jobjectArray jJankDataArray = + env->NewObjectArray(jankData.size(), gJankDataClassInfo.clazz, nullptr); for (size_t i = 0; i < jankData.size(); i++) { // The exposed constants in SurfaceControl are simplified, so we need to translate the // jank type we get from SF to what is exposed in Java. @@ -2225,6 +2245,11 @@ public: return true; } + void removeListener(int64_t afterVsyncId) { + mRemovedVsyncId = (afterVsyncId <= 0) ? 0 : afterVsyncId; + JankDataListener::removeListener(afterVsyncId); + } + private: JNIEnv* getEnv() { @@ -2235,6 +2260,7 @@ private: JavaVM* mVm; jobject mOnJankDataListenerWeak; + int64_t mRemovedVsyncId; }; static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz, diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp index 6c72544a7958b6dcf664a77bd24549ca6cc5fa5a..76ead2a3ca31404f2cd5c58b38bd0eb2253796f4 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -204,7 +203,8 @@ bool punchHoles(const char *filePath, const uint64_t offset, return true; } -bool punchHolesInElf64(const char *filePath, const uint64_t offset) { +bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, + std::vector &programHeaders) { // Open Elf file Elf64_Ehdr ehdr; std::ifstream inputStream(filePath, std::ifstream::in); @@ -227,11 +227,6 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) { uint64_t programHeaderOffset = ehdr.e_phoff; uint16_t programHeaderNum = ehdr.e_phnum; - IF_ALOGD() { - ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu", - filePath, programHeaderOffset, programHeaderNum); - } - // if this is a zip file, also consider elf offset inside a file uint64_t phOffset; if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) { @@ -240,7 +235,6 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) { } inputStream.seekg(phOffset); - std::vector programHeaders; for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) { Elf64_Phdr header; inputStream.read((char *)&header, sizeof(header)); @@ -254,6 +248,15 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) { programHeaders.push_back(header); } + return true; +} + +bool punchHolesInElf64(const char *filePath, const uint64_t offset) { + std::vector programHeaders; + if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) { + ALOGE("Failed to read program headers from ELF file."); + return false; + } return punchHoles(filePath, offset, programHeaders); } diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h index 52445e2b4229531a6228c21d420d288677074c23..4a95686c5a0c1ddf40391a4a527f7d9cbadff6ad 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.h +++ b/core/jni/com_android_internal_content_FileSystemUtils.h @@ -15,8 +15,11 @@ */ #pragma once +#include #include +#include + namespace android { /* @@ -35,4 +38,11 @@ bool punchHolesInElf64(const char* filePath, uint64_t offset); */ bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen); +/* + * This function reads program headers from ELF file. ELF can be specified with file path directly + * or it should be at offset inside Apk. Program headers passed to function is populated. + */ +bool getLoadSegmentPhdrs(const char* filePath, const uint64_t offset, + std::vector& programHeaders); + } // namespace android \ No newline at end of file diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 7fca1175f3d4e62ae8694f2b364aee2945fefd1c..1a0328338980eb787e34f49eae8e751639ba5706 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -88,6 +88,7 @@ extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_SystemClock(JNIEnv* env); extern int register_android_os_SystemProperties(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv* env); +extern int register_android_text_Hyphenator(JNIEnv* env); extern int register_android_util_EventLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_util_jar_StrictJarFile(JNIEnv* env); @@ -133,6 +134,7 @@ static const std::unordered_map gRegJNIMap = { {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)}, {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)}, {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)}, + {"android.text.Hyphenator", REG_JNI(register_android_text_Hyphenator)}, {"android.util.EventLog", REG_JNI(register_android_util_EventLog)}, {"android.util.Log", REG_JNI(register_android_util_Log)}, {"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d09802a91edc6eb6686e9f364d997016af32f40a..7fcbf19d137fb6caa9514cfb006c0eb0506300ca 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -914,13 +914,26 @@ android:featureFlag="android.provider.user_keys" /> + android:protectionLevel="internal|role" + android:featureFlag="!android.provider.new_default_account_api_enabled"/> + + + @@ -1080,6 +1093,52 @@ + + + + + + + + + + + + + + + + + @@ -4144,6 +4203,37 @@ + + + + + + + + + + + + - +

    Protection Level: signature|privileged|appop --> + android:protectionLevel="signature|privileged|appop" + android:featureFlag="com.android.settingslib.flags.write_system_preference_permission_enabled" /> @@ -5497,7 +5587,8 @@ - @@ -7844,7 +7935,31 @@ + android:protectionLevel="signature|installer" + android:featureFlag="!android.content.pm.sdk_dependency_installer" /> + + + + + + + + + @@ -8335,6 +8450,26 @@ android:protectionLevel="signature|knownSigner" android:knownCerts="@array/config_healthConnectMigrationKnownSigners" /> + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml new file mode 100644 index 0000000000000000000000000000000000000000..b7fe454e09d417a81d20ab73f55ce2e86f292ab6 --- /dev/null +++ b/core/res/res/layout/notification_2025_template_header.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml index 82920bad95cd552a0e979583852bdfedc35faaf6..149a5a9568f28a827038054eeb4e6322d3b16e24 100644 --- a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml +++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml @@ -34,12 +34,14 @@ android:maxDrawableWidth="@dimen/notification_icon_circle_size" android:maxDrawableHeight="@dimen/notification_icon_circle_size" /> - "Laat die program toe om relatiewe posisie tussen ultrabreëbandtoestelle in die omtrek te bepaal" "om interaksie met wi‑fi-toestelle in die omtrek te hê" "Laat die program toe om op toestelle in die omtrek te adverteer, aan hulle te koppel en hul relatiewe posisie te bepaal" + + + + "Voorkeur-NFC-betalingdiensinligting" "Laat die program toe om voorkeur-NFC-betalingdiensinligting soos geregistreerde hulpmiddels en roetebestemming te kry." "beheer kortveldkommunikasie" diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 91b4ef01701443b5ac5d2154234c803818cfee1e..c30a5c853421a69caf0ce6eb39763c38d2042bdf 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -612,6 +612,10 @@ "በአቅራቢያ ባሉ ልዕለ-ሰፊ ባንድ መሣሪያዎች መካከል ያለውን አንጻራዊ አቀማመጣቸውን ለማወቅ ንዲችል ለመተግበሪያው ይፍቀዱ" "በአቅራቢያ ካሉ የWi‑Fi መሣሪያዎች ጋር መስተጋብር መፍጠር" "መተግበሪያው በአቅራቢያ ያሉ የWi-Fi መሣሪያዎች አንጻራዊ ቦታን እንዲያሳውቅ፣ እንዲያገናኝ እና እንዲያውቅ ያስችለዋል" + + + + "ተመራጭ NFC የክፍያ አገልግሎት መረጃ" "እንደ የተመዘገቡ እርዳታዎች እና የጉዞ መሥመር መዳረሻ የመሳሰለ ተመራጭ nfc የክፍያ አገልግሎት መረጃን ለማግኘት ለመተግበሪያው ያፈቅድለታል።" "ቅርብ የግኑኙነትመስክ (NFC) ተቆጣጠር" diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 6e5dcd36fd3184b42e8f35c72fa8999edb0e851b..f2080cbbe3d912a02e443189857cacc870245278 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -616,6 +616,10 @@ "يسمح هذا الإذن للتطبيق بتحديد الموضع النسبي بين الأجهزة المجاورة التي تستخدم النطاق الواسع جدًا." "‏التفاعل مع أجهزة Wi‑Fi المجاورة" "‏للسماح للتطبيق بعرض الإعلانات والاتصال بالأجهزة الأخرى وتحديد الموقع النسبي لأجهزة Wi-Fi المجاورة." + + + + "‏معلومات الخدمات المدفوعة باستخدام الاتصال قصير المدى NFC المفضّل" "‏يسمح هذا الإذن للتطبيق بالحصول على معلومات الخدمات المدفوعة باستخدام الاتصال قصير المدى NFC المفضّل، مثلاً المساعدات المسجّلة ووجهة المسار." "التحكم في اتصال الحقل القريب" @@ -2435,54 +2439,30 @@ "تفعيل" "رجوع" "بانتظار الإزالة من الأرشيف…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "تتوفّر الآن ميزة \"اتصالات طوارئ بالقمر الصناعي\"" + "‏يمكنك مراسلة خدمات الطوارئ في حال عدم توفّر شبكة جوّال أو شبكة Wi-Fi. ولإجراء ذلك، يجب ضبط تطبيق \"رسائل Google\" ليصبح تطبيق المراسلة التلقائي." + "ميزة \"اتصالات طوارئ بالقمر الصناعي\" غير متاحة" + "ميزة \"اتصالات طوارئ بالقمر الصناعي\" غير متاحة على هذا الجهاز" + "لم يتم ضبط إعدادات ميزة \"اتصالات طوارئ بالقمر الصناعي\"" + "يُرجى التأكّد من أنّ جهازك متصل بالإنترنت ومحاولة ضبط الميزة مرة أخرى" + "ميزة \"اتصالات طوارئ بالقمر الصناعي\" غير متوفّرة" + "لا تتوفّر ميزة \"اتصالات طوارئ بالقمر الصناعي\" في هذا البلد أو هذه المنطقة" + "لم يتم ضبط إعدادات ميزة \"اتصالات طوارئ بالقمر الصناعي\"" + "‏للمراسلة عبر القمر الصناعي، عليك ضبط تطبيق \"رسائل Google\" ليصبح تطبيق المراسلة التلقائي" + "ميزة \"اتصالات طوارئ بالقمر الصناعي\" غير متوفّرة" + "لمعرفة ما إذا كانت ميزة \"اتصالات طوارئ بالقمر الصناعي\" متاحة في هذا البلد أو هذه المنطقة، فعِّل إعدادات الموقع الجغرافي" + "ميزة \"المراسلة عبر القمر الاصطناعي\" متاحة" + "‏يمكنك إرسال رسائل عبر القمر الصناعي في حال عدم توفّر شبكة جوّال أو شبكة Wi-Fi. ولإجراء ذلك، يجب ضبط تطبيق \"رسائل Google\" ليصبح تطبيق المراسلة التلقائي." + "ميزة \"المراسلة عبر القمر الاصطناعي\" غير متاحة" + "ميزة \"المراسلة عبر القمر الاصطناعي\" غير متاحة على هذا الجهاز" + "لم يتم ضبط إعدادات ميزة \"المراسلة عبر القمر الاصطناعي\"" + "يُرجى التأكّد من أنّ جهازك متصل بالإنترنت ومحاولة ضبط الميزة مرة أخرى" + "ميزة \"المراسلة عبر القمر الاصطناعي\" غير متاحة" + "لا تتوفّر ميزة \"المراسلة عبر القمر الاصطناعي\" في هذا البلد أو هذه المنطقة" + "لم يتم ضبط إعدادات ميزة \"المراسلة عبر القمر الاصطناعي\"" + "‏للمراسلة عبر القمر الصناعي، عليك ضبط تطبيق \"رسائل Google\" ليصبح تطبيق المراسلة التلقائي" + "ميزة \"المراسلة عبر القمر الاصطناعي\" غير متاحة" + "لمعرفة ما إذا كانت ميزة \"المراسلة عبر القمر الاصطناعي\" متاحة في هذا البلد أو هذه المنطقة، فعِّل إعدادات الموقع الجغرافي" "إعادة إعداد ميزة \"فتح الجهاز ببصمة الإصبع\"" "لا يمكن بعد الآن التعرّف على \"%s\"." "لا يمكن بعد الآن التعرّف على \"%1$s\" و\"%2$s\"." diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 1af7810acfeb21b038ed08746ac0ad8ef709b9a0..1f207d571c5afd37bf5b58a9ef76617e874cb8bd 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -612,6 +612,10 @@ "এপ্‌টোক নিকটৱৰ্তী আল্ট্ৰা-ৱাইডবেণ্ড ডিভাইচসমূহৰ মাজৰ আপেক্ষিক স্থান নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ক" "নিকটৱৰ্তী ৱাই-ফাই ডিভাইচসমূহৰ সৈতে ভাব বিনিময় কৰক" "এপ্‌টোক বিজ্ঞাপন প্ৰচাৰাভিযান কৰিবলৈ, সংযোগ কৰিবলৈ আৰু নিকটৱৰ্তী ৱাই-ফাই ডিভাইচৰ আপেক্ষিক স্থান নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে" + + + + "অগ্ৰাধিকাৰ দিয়া NFC পৰিশোধ সেৱাৰ তথ্য" "এপ্‌টোক অগ্ৰাধিকাৰ দিয়া nfc পৰিশোধ সেৱাৰ পঞ্জীকৃত সহায়কসমূহ আৰু পৰিশোধ কৰিব লগা লক্ষ্যস্থান দৰে তথ্য পাবলৈ অনুমতি দিয়ে।" "নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক" @@ -2431,54 +2435,30 @@ "অন কৰক" "উভতি যাওক" "বিবেচনাধীন হৈ আছে..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "উপগ্ৰহ SOS সুবিধাটো এতিয়া উপলব্ধ" + "যদি ম’বাইল বা ৱাই-ফাই নেটৱৰ্ক নাথাকিলেও আপুনি জৰুৰীকালীন সেৱাসমূহলৈ বার্তা পঠিয়াব পাৰে। Google Messages আপোনাৰ ডিফ’ল্ট বাৰ্তা আদান-প্ৰদান কৰা এপ্‌ হ’বই লাগিব।" + "উপগ্ৰহ SOS সুবিধা সমৰ্থিত নহয়" + "এই ডিভাইচটোত উপগ্ৰহ SOS সুবিধা সমৰ্থিত নহয়" + "উপগ্ৰহ SOS সুবিধা ছেট আপ কৰা হোৱা নাই" + "আপুনি ইণ্টাৰনেটৰ সৈতে সংযুক্ত হৈ থকাটো নিশ্চিত কৰক আৰু পুনৰ ছেটআপ কৰিবলৈ চেষ্টা কৰক" + "উপগ্ৰহ SOS সুবিধা উপলব্ধ নহয়" + "এই দেশ বা অঞ্চলত উপগ্ৰহ SOS সুবিধা উপলব্ধ নহয়" + "উপগ্ৰহ SOS সুবিধা ছেট আপ কৰা হোৱা নাই" + "উপগ্ৰহৰ জৰিয়তে বাৰ্তা পঠিয়াবলৈ, Google Messagesক আপোনাৰ ডিফ’ল্ট বাৰ্তা আদান-প্ৰদান কৰা এপ্‌ হিচাপে ছেট কৰক" + "উপগ্ৰহ SOS সুবিধা উপলব্ধ নহয়" + "এই দেশ বা অঞ্চলত উপগ্ৰহ SOS সুবিধা উপলব্ধ হয় নে নহয় পৰীক্ষা কৰিবলৈ, অৱস্থানৰ ছেটিং অন কৰক" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা উপলব্ধ" + "ম’বাইল বা ৱাই-ফাই নেটৱৰ্ক নাথাকিলেও আপুনি উপগ্ৰহৰ জৰিয়তে বার্তা পঠিয়াব পাৰে। Google Messages আপোনাৰ ডিফ’ল্ট বাৰ্তা আদান-প্ৰদান কৰা এপ্‌ হ’বই লাগিব।" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা সমৰ্থিত নহয়" + "এই ডিভাইচটোত উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা সমৰ্থিত নহয়" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা ছেট আপ কৰা হোৱা নাই" + "আপুনি ইণ্টাৰনেটৰ সৈতে সংযুক্ত হৈ থকাটো নিশ্চিত কৰক আৰু পুনৰ ছেটআপ কৰিবলৈ চেষ্টা কৰক" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা উপলব্ধ নহয়" + "এই দেশ বা অঞ্চলত উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা উপলব্ধ নহয়" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা ছেট আপ কৰা হোৱা নাই" + "উপগ্ৰহৰ জৰিয়তে বাৰ্তা পঠিয়াবলৈ, Google Messagesক আপোনাৰ ডিফ’ল্ট বাৰ্তা আদান-প্ৰদান কৰা এপ্‌ হিচাপে ছেট কৰক" + "উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা উপলব্ধ নহয়" + "এই দেশ বা অঞ্চলত উপগ্ৰহৰ দ্বাৰা বাৰ্তা বিনিময়ৰ সুবিধা উপলব্ধ হয় নে নহয় পৰীক্ষা কৰিবলৈ, অৱস্থানৰ ছেটিং অন কৰক" "ফিংগাৰপ্ৰিণ্ট আনলক পুনৰ ছেট আপ কৰক" "%s আৰু চিনাক্ত কৰিব নোৱাৰি।" "%1$s আৰু %2$s আৰু চিনাক্ত কৰিব নোৱাৰি।" diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 1ba96fea7a197e93851caf9a43c2ecb080219ad0..07216f55bdcdd7c72c1815404f824bdf1b6adf78 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -612,6 +612,10 @@ "Tətbiqə yaxınlıqdakı Ultra Genişzolaqlı cihazları arasında nisbi mövqeyi təyin etməyə icazə verin" "yaxınlıqdakı Wi-Fi cihazları ilə əlaqə qurmaq" "Tətbiqə yaxınlıqdakı Wi-Fi cihazlarında reklam etmək, onlara qoşulmaq və nisbi mövqeyini təyin etmək icazəsi verir" + + + + "Tərcih edilən NFC ödəniş xidməti məlumatı" "Tətbiqə qeydiyyatdan keçmiş yardım və marşrut təyinatı kimi tərcih edilən nfc ödəniş xidməti məlumatını əldə etmək icazəsi verir." "Near Field Communication\'ı kontrol et" diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 4c78d30c874e0e78478749075381a6fd0c36cdaa..ac6a47fd8f84fd76a22e0c7cc408c9ea5f09baf7 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -613,6 +613,10 @@ "Dozvoljava aplikaciji da određuje relativnu razdaljinu između uređaja ultra-širokog pojasa u blizini" "interakcija sa WiFi uređajima u blizini" "Dozvoljava aplikaciji da se oglašava, povezuje i utvrđuje relativnu poziciju WiFi uređaja u blizini" + + + + "Informacije o željenoj NFC usluzi za plaćanje" "Dozvoljava aplikaciji da preuzima informacije o željenoj NFC usluzi za plaćanje, poput registrovanih identifikatora aplikacija i odredišta preusmeravanja." "kontrola komunikacije u užem polju (Near Field Communication)" @@ -2432,54 +2436,30 @@ "Uključi" "Nazad" "Na čekanju..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Hitna pomoć preko satelita je sada dostupna" + "Možete da šaljete poruke hitnim službama ako nemate pristup mobilnoj ni WiFi mreži. Google Messages mora da bude podrazumevana aplikacija za razmenu poruka." + "Hitna pomoć preko satelita nije podržana" + "Hitna pomoć preko satelita nije podržana na ovom uređaju" + "Hitna pomoć preko satelita nije podešena" + "Proverite da li ste povezani na internet i probajte ponovo da podesite" + "Hitna pomoć preko satelita nije dostupna" + "Hitna pomoć preko satelita nije dostupna u ovoj zemlji ili regionu" + "Hitna pomoć preko satelita nije podešena" + "Da biste slali poruke preko satelita, podesite Google Messages kao podrazumevanu aplikaciju za razmenu poruka" + "Hitna pomoć preko satelita nije dostupna" + "Da biste proverili da li je hitna pomoć preko satelita dostupna u ovoj zemlji ili regionu, uključite podešavanja lokacije" + "Razmena poruka preko satelita je dostupna" + "Možete da šaljete poruke preko satelita ako nemate pristup mobilnoj ni WiFi mreži. Google Messages mora da bude podrazumevana aplikacija za razmenu poruka." + "Razmena poruka preko satelita nije podržana" + "Razmena poruka preko satelita nije podržana na ovom uređaju" + "Razmena poruka preko satelita nije podešena" + "Proverite da li ste povezani na internet i probajte ponovo da podesite" + "Razmena poruka preko satelita nije dostupna" + "Razmena poruka preko satelita nije dostupna u ovoj zemlji ili regionu" + "Razmena poruka preko satelita nije podešena" + "Da biste slali poruke preko satelita, podesite Google Messages kao podrazumevanu aplikaciju za razmenu poruka" + "Razmena poruka preko satelita nije dostupna" + "Da biste proverili da li je razmena poruka preko satelita dostupna u ovoj zemlji ili regionu, uključite podešavanja lokacije" "Ponovo podesite otključavanje otiskom prsta" "%s više ne može da se prepozna." "%1$s i %2$s više ne mogu da se prepoznaju." diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 549ec78cc7d802055f135d1d38e89bc3dec214fd..f492bfa4cc7bb69c5677af79d5ff508477ac167c 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -614,6 +614,10 @@ "Дазволіць праграме вызначаць адлегласць паміж прыладамі паблізу, якія выкарыстоўваюць звышшырокапалосную сувязь" "узаемадзейнічаць з прыладамі з Wi‑Fi паблізу" "Праграма зможа адпраўляць даныя на прылады Wi-Fi паблізу, падключацца да іх і вызначаць іх месцазнаходжанне" + + + + "Інфармацыя пра прыярытэтны сэрвіс аплаты NFC" "Дазваляе праграме атрымаць доступ да інфармацыі пра прыярытэтны сэрвіс аплаты NFC, напрыклад зарэгістраваныя ідэнтыфікатары праграм і маршруты адпраўкі даных." "кантроль Near Field Communication" @@ -2433,54 +2437,30 @@ "Уключыць" "Назад" "У чаканні..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Экстраннае спадарожнікавае падключэнне зараз даступнае" + "У вас ёсць магчымасць адпраўляць паведамленні ў экстранныя службы, калі адсутнічае падключэнне да мабільнай сеткі ці сеткі Wi-Fi. Упэўніцеся, што ў якасці стандартнай выбрана праграма \"Google Паведамленні\"." + "Экстраннае спадарожнікавае падключэнне не падтрымліваецца" + "Экстраннае спадарожнікавае падключэнне не падтрымліваецца на гэтай прыладзе" + "Экстраннае спадарожнікавае падключэнне не наладжана" + "Праверце падключэнне да інтэрнэту і паўтарыце наладжванне" + "Экстраннае спадарожнікавае падключэнне недаступнае" + "Функцыя экстраннага спадарожнікавага падключэння недаступная ў гэтай краіне або рэгіёне" + "Экстраннае спадарожнікавае падключэнне не наладжана" + "Каб выкарыстоўваць абмен паведамленнямі па спадарожнікавай сувязі, праграма \"Google Паведамленні\" павінна быць выбрана як стандартная праграма абмену паведамленнямі" + "Экстраннае спадарожнікавае падключэнне недаступнае" + "Уключыце налады месцазнаходжання, каб праверыць, ці даступнае экстраннае спадарожнікавае падключэнне ў гэтай краіне ці рэгіёне" + "Абмен паведамленнямі па спадарожнікавай сувязі даступны" + "Вы можаце абменьвацца паведамленнямі па спадарожнікавай сувязі, калі падключэнне да мабільнай сеткі ці сеткі Wi-Fi адсутнічае. Упэўніцеся, што ў якасці стандартнай выбрана праграма \"Google Паведамленні\"." + "Абмен паведамленнямі па спадарожнікавай сувязі не падтрымліваецца" + "Абмен паведамленнямі па спадарожнікавай сувязі не падтрымліваецца на гэтай прыладзе" + "Абмен паведамленнямі па спадарожнікавай сувязі не наладжаны" + "Праверце падключэнне да інтэрнэту і паўтарыце наладжванне" + "Абмен паведамленнямі па спадарожнікавай сувязі недаступны" + "Абмен паведамленнямі па спадарожнікавай сувязі недаступны ў гэтай краіне або рэгіёне" + "Абмен паведамленнямі па спадарожнікавай сувязі не наладжаны" + "Каб выкарыстоўваць абмен паведамленнямі па спадарожнікавай сувязі, праграма \"Google Паведамленні\" павінна быць выбрана як стандартная праграма абмену паведамленнямі" + "Абмен паведамленнямі па спадарожнікавай сувязі недаступны" + "Каб праверыць, ці даступны абмен паведамленнямі па спадарожнікавай сувязі ў гэтай краіне ці рэгіёне, уключыце налады месцазнаходжання" "Наладзіць разблакіроўку адбіткам пальца паўторна" "Адбітак пальца \"%s\" больш не можа быць распазнаны." "Адбіткі пальцаў \"%1$s\" і \"%2$s\" больш не могуць быць распазнаны." diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 40aae5144448102140bcf5cb5098f6579799a5aa..c8b171af5bc99365e9ec9c346282333324863249 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -612,6 +612,10 @@ "Разрешаване на приложението да определя относителната позиция между устройствата с ултрашироколентови сигнали в близост" "взаимодействие с устройствата с Wi-Fi в близост" "Разрешава на приложението да рекламира, да се свързва и да определя относителната позиция на устройствата с Wi-Fi в близост" + + + + "Информация за предпочитаната услуга за плащане чрез NFC" "Дава възможност на приложението да получава информация за предпочитаната услуга за плащане чрез NFC, като например регистрирани помощни средства и местоназначение." "контролиране на комуникацията в близкото поле" diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 32077d8a43744c47cdd0f81f73ebea8ab7b9fd3a..0e103cc424b0aa4e740beb6e9f2c4e0e34cb41d9 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -612,6 +612,10 @@ "অ্যাপকে আশেপাশের Ultra-Wideband ডিভাইসগুলির আপেক্ষিক অবস্থান নির্ণয় করার অনুমতি দিন" "আশপাশের ওয়াই-ফাই ডিভাইসের সাথে ইন্টার‍্যাক্ট করুন" "এটির ফলে অ্যাপ আশপাশের ওয়াই-ফাই ডিভাইসের তথ্য দেখতে, তাদের সাথে কানেক্ট করতে এবং তা কত দূরত্বে আছে সেটি জানতে পারবে" + + + + "পছন্দের NFC পেমেন্ট পরিষেবার তথ্য" "অ্যাপের মাধ্যমে পছন্দসই এনএফসি পেমেন্ট পরিষেবার তথ্য, যেমন রেজিস্ট্রার করার সহায়তা এবং রুট ডেস্টিনেশন সম্পর্কিত তথ্য অ্যাক্সেস করার অনুমতি দেয়।" "নিয়ার ফিল্ড কমিউনিকেশন নিয়ন্ত্রণ করে" @@ -2431,54 +2435,30 @@ "চালু করুন" "ফিরে যান" "বাকি আছে…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "স্যাটেলাইট SOS এখন উপলভ্য" + "কোনও মোবাইল বা ওয়াই-ফাই নেটওয়ার্ক না থাকলেও আপনি জরুরি পরিষেবাতে মেসেজ করতে পারবেন। Google Messages অবশ্যই আপনার ডিফল্ট মেসেজিং অ্যাপ হতে হবে।" + "স্যাটেলাইট SOS কাজ করে না" + "এই ডিভাইসে স্যাটেলাইট SOS কাজ করে না" + "স্যাটেলাইট SOS সেট আপ করা হয়নি" + "আপনার ডিভাইসে ইন্টারনেট কানেকশন আছে কিনা দেখে নিন এবং আবার সেটআপ করার চেষ্টা করুন" + "স্যাটেলাইট SOS উপলভ্য নেই" + "এই দেশ অথবা অঞ্চলে স্যাটেলাইট SOS উপলভ্য নেই" + "স্যাটেলাইট SOS সেট আপ করা নেই" + "স্যাটেলাইটের সাহায্যে মেসেজ পাঠাতে, আপনার ডিফল্ট মেসেজিং অ্যাপ হিসেবে Google Messages সেট করুন" + "স্যাটেলাইট SOS উপলভ্য নেই" + "এই দেশে বা অঞ্চলে স্যাটেলাইট SOS উপলভ্য আছে কিনা দেখতে, লোকেশন সেটিংস চালু করুন" + "\'স্যাটেলাইট মেসেজিং\' ফিচার উপলভ্য" + "কোনও মোবাইল বা ওয়াই-ফাই নেটওয়ার্ক না থাকলে, স্যাটেলাইটের মাধ্যমে মেসেজ করতে পারবেন। Google Messages অবশ্যই আপনার ডিফল্ট মেসেজিং অ্যাপ হতে হবে।" + "\'স্যাটেলাইট মেসেজিং\' ফিচার কাজ করে না" + "এই ডিভাইসে \'স্যাটেলাইট মেসেজিং\' ফিচার কাজ করে না" + "\'স্যাটেলাইট মেসেজিং\' ফিচার সেট আপ করা নেই" + "আপনার ডিভাইসে ইন্টারনেট কানেকশন আছে কিনা দেখে নিন এবং আবার সেটআপ করার চেষ্টা করুন" + "\'স্যাটেলাইট মেসেজিং\' ফিচার উপলভ্য নেই" + "এই দেশে বা অঞ্চলে \'স্যাটেলাইট মেসেজিং\' ফিচার উপলভ্য নেই" + "\'স্যাটেলাইট মেসেজিং\' ফিচার সেট আপ করা নেই" + "স্যাটেলাইটের সাহায্যে মেসেজ পাঠাতে, আপনার ডিফল্ট মেসেজিং অ্যাপ হিসেবে Google Messages সেট করুন" + "\'স্যাটেলাইট মেসেজিং\' ফিচার উপলভ্য নেই" + "\'স্যাটেলাইট মেসেজিং\' ফিচার এই দেশে বা অঞ্চলে উপলভ্য আছে কিনা দেখতে, লোকেশন সেটিংস চালু করুন" "\'ফিঙ্গারপ্রিন্ট আনলক\' আবার সেট-আপ করুন" "%s আর শনাক্ত করা যাবে না।" "%1$s%2$s আর শনাক্ত করা যাবে না।" diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 266dee2b984a5a7ea5ab06538817914053e05aef..202afdeb943846d36b959117021fcb017dd8aad6 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -613,6 +613,10 @@ "Dozvolite aplikaciji da odredi relativni položaj između uređaja ultra širokog opsega u blizini" "stupanje u interakciju s WiFi uređajima u blizini" "Dozvoljava aplikaciji da se oglašava, povezuje i određuje relativni položaj WiFi uređaja u blizini" + + + + "Informacije o preferiranoj usluzi plaćanja putem NFC-a" "Dozvoljava aplikaciji da dobije informacije o preferiranoj usluzi plaćanja putem NFC-a kao što su registrirana pomagala i odredište rute." "upravljanje NFC-om" @@ -2432,54 +2436,30 @@ "Uključi" "Nazad" "Na čekanju…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Hitna pomoć putem satelita je sada dostupna" + "Možete razmjenjivati poruke s hitnim službama ako nemate mobilnu ili WiFi mrežu. Google Messages mora biti zadana aplikacija za razmjenu poruka." + "Hitna pomoć putem satelita nije podržana" + "Hitna pomoć putem satelita nije podržana na uređaju" + "Hitna pomoć putem satelita nije postavljena" + "Provjerite jeste li povezani s internetom i ponovo pokušajte postaviti uslugu" + "Hitna pomoć putem satelita nije dostupna" + "Hitna pomoć putem satelita trenutno nije dostupna u ovoj zemlji ili regiji" + "Hitna pomoć putem satelita nije postavljena" + "Da razmjenjujete poruke putem satelita, postavite Google Messages kao zadanu aplikaciju za razmjenu poruka" + "Hitna pomoć putem satelita nije dostupna" + "Da provjerite je li hitna pomoć putem satelita dostupna u vašoj zemlji ili regiji, uključite postavke lokacije" + "Satelitska razmjena poruka je dostupna" + "Možete razmjenjivati poruke putem satelita ako nemate mobilnu ili WiFi mrežu. Google Messages mora biti zadana aplikacija za razmjenu poruka." + "Satelitska razmjena poruka nije podržana" + "Satelitska razmjena poruka nije podržana na uređaju" + "Satelitska razmjena poruka nije postavljena" + "Provjerite jeste li povezani s internetom i ponovo pokušajte postaviti uslugu" + "Satelitska razmjena poruka nije dostupna" + "Satelitska razmjena poruka nije dostupna u ovoj zemlji ili regiji" + "Satelitska razmjena poruka nije postavljena" + "Da razmjenjujete poruke putem satelita, postavite Google Messages kao zadanu aplikaciju za razmjenu poruka" + "Satelitska razmjena poruka nije dostupna" + "Da provjerite je li satelitska razmjena poruka dostupna u vašoj zemlji ili regiji, uključite postavke lokacije" "Ponovo postavite otključavanje otiskom prsta" "%s se više ne može prepoznati." "%1$s i %2$s se više ne mogu prepoznati." @@ -2489,7 +2469,7 @@ "Vaš model lica se više ne može prepoznati. Ponovo postavite otključavanje licem." "Postavite" "Ne sada" - "Alarm za: %s" + "Alarm za korisnika %s" "Prebaci na drugog korisnika" "Isključi zvuk" "Dodirnite da isključite zvuk" diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 691126bb58ba1e6b0375892eab7beb8402363666..8b28cd7891c846cde2ebc193fee029b35911019a 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -613,6 +613,10 @@ "Permet que l\'aplicació determini la posició relativa entre els dispositius de banda ultraampla propers" "interaccionar amb els dispositius Wi‑Fi propers" "Permet que l\'aplicació s\'anunciï i es connecti als dispositius Wi‑Fi propers, i en determini la posició relativa" + + + + "Informació preferent sobre el servei de pagament per NFC" "Permet que l\'aplicació obtingui informació preferent sobre el servei de pagament per NFC, com ara complements registrats i destinacions de rutes." "controlar Comunicació de camp proper (NFC)" diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index f6ee489802a9fd75e3c1deaceea57c04d4716d30..f3a020bbfce48e0ea55d6d38a43b18accabd5a1d 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -614,6 +614,10 @@ "Aplikace bude moci zjišťovat vzájemnou pozici mezi ultra-širokopásmovými zařízeními v okolí" "interakce se zařízeními Wi-Fi v okolí" "Umožňuje aplikaci inzerovat, připojovat se a odhadovat relativní polohu zařízení Wi-Fi v okolí" + + + + "Informace o preferované platební službě NFC" "Umožňuje aplikaci získat informace o preferované platební službě NFC, například o registrovaných pomůckách a cíli směrování." "ovládání technologie NFC" diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index a861e3acfb231da475dbe2078517c5c0b8241945..b481682b044878ca303ac0740ec248cb9da99bef 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -612,6 +612,10 @@ "Tillad, at appen fastlægger den relative position mellem UWB-enheder (Ultra-Wideband) i nærheden" "interagere med Wi‑Fi-enheder i nærheden" "Giver appen tilladelse til at informere om, oprette forbindelse til og fastslå den relative placering af Wi‑Fi-enheder i nærheden" + + + + "Foretrukne oplysninger vedrørende NFC-betalingstjeneste" "Tillader, at appen får foretrukne oplysninger vedrørende NFC-betalingstjeneste, f.eks. registrerede hjælpemidler og rutedestinationer." "administrere Near Field Communication" diff --git a/core/res/res/values-de-feminine/strings.xml b/core/res/res/values-de-feminine/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ef7f3bb8d1931adebf8c5576086948a4a7944d59 --- /dev/null +++ b/core/res/res/values-de-feminine/strings.xml @@ -0,0 +1,24 @@ + + + + + "Eigentümerin" + diff --git a/core/res/res/values-de-masculine/strings.xml b/core/res/res/values-de-masculine/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8c46e7fb174a31b2d3b6ebcb4b1e6948dc8cbc9 --- /dev/null +++ b/core/res/res/values-de-masculine/strings.xml @@ -0,0 +1,24 @@ + + + + + "Eigentümer" + diff --git a/core/res/res/values-de-neuter/strings.xml b/core/res/res/values-de-neuter/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8c46e7fb174a31b2d3b6ebcb4b1e6948dc8cbc9 --- /dev/null +++ b/core/res/res/values-de-neuter/strings.xml @@ -0,0 +1,24 @@ + + + + + "Eigentümer" + diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 58c4c5e93ef123b8687a307ff1ef0fef8d75b358..64afb5adffc42197c28c44fb6c7d5df26198a32e 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -612,6 +612,10 @@ "Ermöglicht der App, die relative Distanz zwischen Ultrabreitband-Geräten in der Nähe zu bestimmen" "Mit WLAN-Geräten in der Nähe interagieren" "Erlaubt der App, Inhalte an WLAN-Geräte in der Nähe zu senden, sich mit ihnen zu verbinden und ihre relative Position zu ermitteln" + + + + "Informationen zum bevorzugten NFC-Zahlungsdienst" "Ermöglicht der App, Informationen zum bevorzugten NFC-Zahlungsdienst abzurufen, etwa registrierte Hilfsmittel oder das Routenziel." "Nahfeldkommunikation steuern" diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 3a32c528d72fb236a6eb93ac4c60e9c8816f4c8e..4ad85d97201a8ee4f9ccc55326fe98536eaeea35 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -612,6 +612,10 @@ "Επιτρέψτε στην εφαρμογή να προσδιορίζει τη σχετική θέση μεταξύ κοντινών συσκευών Ultra-Wideband" "αλληλεπίδραση με κοντινές συσκευές Wi‑Fi" "Επιτρέπει στην εφαρμογή: προβολή διαφημίσεων, σύνδεση και καθορισμό της σχετικής τοποθεσίας των κοντινών συσκευών Wi‑Fi" + + + + "Πληροφορίες προτιμώμενης υπηρεσίας πληρωμών NFC" "Επιτρέπει στην εφαρμογή να λαμβάνει πληροφορίες προτιμώμενης υπηρεσίας πληρωμής NFC, όπως καταχωρημένα βοηθήματα και προορισμό διαδρομής." "ελέγχει την Επικοινωνία κοντινού πεδίου (FNC)" diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 84cc4095457dcf99e5c463fba38d44531a6349fd..a82b567a087e683c198c479e3f29c027958aad7d 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -612,6 +612,10 @@ "Allow the app to determine relative position between nearby ultra-wideband devices" "interact with nearby Wi‑Fi devices" "Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices" + + + + "Preferred NFC payment service information" "Allows the app to get preferred NFC payment service information, such as registered aids and route destination." "control Near-Field Communication" @@ -2431,54 +2435,30 @@ "Turn on" "Go back" "Pending…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Satellite SOS is now available" + "You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite SOS isn\'t supported" + "Satellite SOS isn\'t supported on this device" + "Satellite SOS isn\'t set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite SOS isn\'t available" + "Satellite SOS isn\'t available in this country or region" + "Satellite SOS not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite SOS isn\'t available" + "To check if satellite SOS is available in this country or region, turn on location settings" + "Satellite messaging available" + "You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite messaging not supported" + "Satellite messaging isn\'t supported on this device" + "Satellite messaging not set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite messaging not available" + "Satellite messaging isn\'t available in this country or region" + "Satellite messaging not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite messaging not available" + "To check if satellite messaging is available in this country or region, turn on location settings" "Set up Fingerprint Unlock again" "%s can no longer be recognised." "%1$s and %2$s can no longer be recognised." @@ -2488,7 +2468,7 @@ "Your face model can no longer be recognised. Set up Face Unlock again." "Set up" "Not now" - "Alarm for: %s" + "Alarm for %s" "Switch user" "Mute" "Tap to mute sound" diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 42250049636a96da3694c6a7dae0621c3368d35d..b09a79a63039bc052898640729fd8e90a5d9413b 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -612,6 +612,8 @@ "Allow the app to determine relative position between nearby Ultra-Wideband devices" "interact with nearby Wi‑Fi devices" "Allows the app to advertise, connect, and determine the relative position of nearby Wi‑Fi devices" + "determine relative position between nearby devices" + "Allow the app to determine relative position between nearby devices" "Preferred NFC Payment Service Information" "Allows the app to get preferred nfc payment service information like registered aids and route destination." "control Near Field Communication" diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index a80d0674d2651fcce7e96d77d472b71a3145b489..bf3b985ae6e84dfe33ff84ef0993851639840065 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -612,6 +612,10 @@ "Allow the app to determine relative position between nearby ultra-wideband devices" "interact with nearby Wi‑Fi devices" "Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices" + + + + "Preferred NFC payment service information" "Allows the app to get preferred NFC payment service information, such as registered aids and route destination." "control Near-Field Communication" @@ -2431,54 +2435,30 @@ "Turn on" "Go back" "Pending…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Satellite SOS is now available" + "You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite SOS isn\'t supported" + "Satellite SOS isn\'t supported on this device" + "Satellite SOS isn\'t set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite SOS isn\'t available" + "Satellite SOS isn\'t available in this country or region" + "Satellite SOS not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite SOS isn\'t available" + "To check if satellite SOS is available in this country or region, turn on location settings" + "Satellite messaging available" + "You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite messaging not supported" + "Satellite messaging isn\'t supported on this device" + "Satellite messaging not set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite messaging not available" + "Satellite messaging isn\'t available in this country or region" + "Satellite messaging not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite messaging not available" + "To check if satellite messaging is available in this country or region, turn on location settings" "Set up Fingerprint Unlock again" "%s can no longer be recognised." "%1$s and %2$s can no longer be recognised." @@ -2488,7 +2468,7 @@ "Your face model can no longer be recognised. Set up Face Unlock again." "Set up" "Not now" - "Alarm for: %s" + "Alarm for %s" "Switch user" "Mute" "Tap to mute sound" diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index c123499b6883a2fdc3eace72762bda5f8f152681..5c9c521487509cf911a4981149ed275c55ea180c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -612,6 +612,10 @@ "Allow the app to determine relative position between nearby ultra-wideband devices" "interact with nearby Wi‑Fi devices" "Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices" + + + + "Preferred NFC payment service information" "Allows the app to get preferred NFC payment service information, such as registered aids and route destination." "control Near-Field Communication" @@ -2431,54 +2435,30 @@ "Turn on" "Go back" "Pending…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Satellite SOS is now available" + "You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite SOS isn\'t supported" + "Satellite SOS isn\'t supported on this device" + "Satellite SOS isn\'t set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite SOS isn\'t available" + "Satellite SOS isn\'t available in this country or region" + "Satellite SOS not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite SOS isn\'t available" + "To check if satellite SOS is available in this country or region, turn on location settings" + "Satellite messaging available" + "You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app." + "Satellite messaging not supported" + "Satellite messaging isn\'t supported on this device" + "Satellite messaging not set up" + "Make sure that you\'re connected to the Internet and try setup again" + "Satellite messaging not available" + "Satellite messaging isn\'t available in this country or region" + "Satellite messaging not set up" + "To message by satellite, set Google Messages as your default messaging app" + "Satellite messaging not available" + "To check if satellite messaging is available in this country or region, turn on location settings" "Set up Fingerprint Unlock again" "%s can no longer be recognised." "%1$s and %2$s can no longer be recognised." @@ -2488,7 +2468,7 @@ "Your face model can no longer be recognised. Set up Face Unlock again." "Set up" "Not now" - "Alarm for: %s" + "Alarm for %s" "Switch user" "Mute" "Tap to mute sound" diff --git a/core/res/res/values-es-rUS-feminine/strings.xml b/core/res/res/values-es-rUS-feminine/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..bf181d022f9ade0dbf29addf8cb2582c8bec7d4a --- /dev/null +++ b/core/res/res/values-es-rUS-feminine/strings.xml @@ -0,0 +1,24 @@ + + + + + "Propietaria" + diff --git a/core/res/res/values-es-rUS-masculine/strings.xml b/core/res/res/values-es-rUS-masculine/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b67970f668b530cd7556f3760d00d8fbe7bbeb4 --- /dev/null +++ b/core/res/res/values-es-rUS-masculine/strings.xml @@ -0,0 +1,24 @@ + + + + + "Propietario" + diff --git a/core/res/res/values-es-rUS-neuter/strings.xml b/core/res/res/values-es-rUS-neuter/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b67970f668b530cd7556f3760d00d8fbe7bbeb4 --- /dev/null +++ b/core/res/res/values-es-rUS-neuter/strings.xml @@ -0,0 +1,24 @@ + + + + + "Propietario" + diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 26b4dfa4f2f4be96d31d39feeea47a8252100d53..ab6f6ce07c1d3ac12f7769bee91f8ed1149b7244 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -613,6 +613,10 @@ "Permite que la app determine la posición relativa con dispositivos Ultra Wideband cercanos" "interactuar con dispositivos Wi-Fi cercanos" "Permite que la app muestre anuncios, se conecte y determine la posición relativa de los dispositivos Wi-Fi cercanos" + + + + "Información sobre servicio de pago NFC preferido" "Permite que la app reciba información del servicio de pago NFC preferido, como el servicio de asistencia registrado y el destino de la ruta." "controlar la Transmisión de datos en proximidad" @@ -2432,54 +2436,30 @@ "Activar" "Atrás" "Pendiente…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "La función SOS por satélite ya está disponible" + "Puedes enviar mensajes a los servicios de emergencia si no hay una red móvil o Wi-Fi. Tu app de mensajería predeterminada debe ser Mensajes de Google." + "No se admite la función SOS por satélite" + "Este dispositivo no admite la función SOS por satélite" + "No se configuró la función SOS por satélite" + "Asegúrate de tener conexión a Internet y vuelve a intentar la configuración" + "La función SOS por satélite no está disponible" + "La función SOS por satélite no está disponible en este país o región" + "No se configuró la función SOS por satélite" + "Para enviar mensajes por satélite, establece Mensajes de Google como tu app de mensajería predeterminada" + "La función SOS por satélite no está disponible" + "Para verificar si la función SOS por satélite está disponible en este país o región, activa la configuración de ubicación" + "La Mensajería satelital está disponible" + "Puedes enviar mensajes por satélite si no hay una red móvil o Wi-Fi. Tu app de mensajería predeterminada debe ser Mensajes de Google." + "No se admite la Mensajería satelital" + "Este dispositivo no admite la Mensajería satelital" + "No se configuró la Mensajería satelital" + "Asegúrate de tener conexión a Internet y vuelve a intentar la configuración" + "La Mensajería satelital no disponible" + "La Mensajería satelital no está disponible en este país o región" + "No se configuró la Mensajería satelital" + "Para enviar mensajes por satélite, establece Mensajes de Google como tu app de mensajería predeterminada" + "La Mensajería satelital no disponible" + "Para verificar si la Mensajería satelital está disponible en este país o región, activa la configuración de ubicación" "Vuelve a configurar el Desbloqueo con huellas dactilares" "Ya no se puede reconocer %s." "Ya no se pueden reconocer %1$s y %2$s." @@ -2489,7 +2469,7 @@ "Ya no se puede reconocer tu modelo de rostro. Vuelve a configurar el Desbloqueo facial." "Configurar" "Ahora no" - "Alarma para: %s" + "Alarma para %s" "Cambiar de usuario" "Silenciar" "Presiona para silenciar el sonido" diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 166e2dac1366d45382fe30e9411b1fb145294bfc..23e1ae63573029e4740140f6768b9621656b1bc7 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -613,6 +613,10 @@ "Permite que la aplicación determine la posición relativa de los dispositivos de banda ultraancha cercanos" "interactuar con dispositivos Wi-Fi cercanos" "Permite a la aplicación emitir y conectarse a dispositivos Wi-Fi cercanos y determinar su posición relativa" + + + + "Información sobre el servicio de pago por NFC preferido" "Permite que la aplicación obtenga información sobre el servicio de pago por NFC preferido, como identificadores de aplicación registrados y destinos de rutas." "controlar Comunicación de campo cercano (NFC)" diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index fed97a191c87aca3e79b5ec422afd507d22c2602..d22c93b30ac9c773f7d5c190bc9b8f710d21802d 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -612,6 +612,10 @@ "Võimaldab rakendusel määrata lähedalasuvate ülilairibaühendust kasutavate seadmete suhtelise kauguse üksteisest" "Läheduses olevate WiFi-seadmetega suhtlemine" "Lubab rakendusel läheduses olevatele WiFi-seadmetele reklaamida, nendega ühenduse luua ja määrata nende suhteline asend" + + + + "Eelistatud NFC-makseteenuse teave" "Võimaldab rakendusel hankida eelistatud NFC-makseteenuse teavet (nt registreeritud abi ja marsruudi sihtkoht)." "lähiväljaside juhtimine" diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 26e41140f3915e9befd4e87ff97ca5c64e9b6ea9..914dfb72c06e3092b1292ba40cc130e8096497c2 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -612,6 +612,10 @@ "Banda ultrazabala darabilten inguruko gailuen arteko distantzia erlatiboa zehazteko baimena ematen dio aplikazioari" "inguruko wifi-gailuekin interakzioan jardun" "Inguruko wifi-gailuetan iragartzeko, haiekin konektatzeko eta haien kokapena zehazteko baimena ematen dio aplikazioari" + + + + "NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa" "NFC bidezko ordainketa-zerbitzu lehenetsiari buruzko informazioa jasotzeko baimena ematen dio aplikazioari, hala nola erregistratutako laguntzaileak eta ibilbidearen helmuga." "kontrolatu Near Field Communication komunikazioa" diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index a8659eb289de453d55e4cfa0491f5c2af3077342..c3e4b4680752f9abff38391b97ea6883d2cd57da 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -612,6 +612,10 @@ "به برنامه اجازه داده می‌شود موقعیت نسبی بین دستگاه‌های «فراپهن‌باند» اطراف را مشخص کند" "‏برقراری تعامل با دستگاه‌های Wi-Fi اطراف" "‏به برنامه اجازه می‌دهد در دستگاه‌های Wi-Fi اطراف تبلیغ کند، به آن‌ها متصل شود، و موقعیت نسبی آن‌ها را تشخیص دهد" + + + + "‏اطلاعات ترجیحی سرویس پرداخت NFC" "‏به برنامه اجازه می‌دهد اطلاعات ترجیحی سرویس پرداخت NFC، مانند کمک‌های ثبت‌شده و مقصد مسیر را دریافت کند." "کنترل ارتباط راه نزدیک" diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 1f4372abb677de0cc6285369067937dfcf003f34..fb7c4fed3f476781633e71d3f03be1fa8da7026b 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -612,6 +612,10 @@ "Sallii sovelluksen määrittää UVB-taajuutta käyttävien laitteiden sijainnin suhteessa toisiinsa" "käyttää lähellä olevia Wi-Fi-laitteita" "Sallii sovelluksen ilmoittaa ja määrittää lähellä olevien Wi-Fi-laitteiden suhteellisen sijainnin sekä yhdistää niihin" + + + + "Ensisijaiset NFC-maksupalvelutiedot" "Sallii sovelluksen noutaa tietoja rekisteröidyistä sovellustunnuksista, maksureitin kohteesta ja muita ensisijaisia NFC-maksupalvelutietoja." "hallitse Near Field Communication -tunnistusta" diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index f7f1474fca7b17a12f8c395029750abdfc1bf7da..18ef10995e90bebe0935bb11757ed39e33b1c9da 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -613,6 +613,10 @@ "Autorisez l\'appli à déterminer la position relative entre des appareils à bande ultralarge à proximité" "interagir avec les appareils Wi-Fi à proximité" "Permet à l\'appli de diffuser des annonces, de se connecter et de déterminer la position relative des appareils Wi-Fi à proximité" + + + + "Information sur le service préféré de paiement CCP" "Permet à l\'appli d\'obtenir de l\'information sur le service préféré de paiement CCP comme les aides enregistrées et la route de destination." "gérer la communication en champ proche" @@ -2432,54 +2436,30 @@ "Activer" "Retour" "En attente…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS par satellite est maintenant accessible" + "Vous pouvez envoyer un message aux services d\'urgence s\'il n\'y a pas de réseau mobile ou Wi-Fi. Messages de Google doit être votre appli de messagerie par défaut." + "SOS par satellite n\'est pas prise en charge" + "SOS par satellite n\'est pas prise en charge sur cet appareil" + "SOS par satellite n\'est pas configurée" + "Assurez-vous que vous êtes connecté à Internet et réessayez d\'effectuer la configuration" + "SOS par satellite n\'est pas accessible" + "SOS par satellite n\'est pas accessible dans ce pays ou cette région" + "SOS par satellite n\'est pas configurée" + "Pour envoyer des messages par satellite, définissez Messages de Google comme appli de messagerie par défaut" + "SOS par satellite n\'est pas accessible" + "Pour vérifier si SOS par satellite est accessible dans ce pays ou cette région, activez les paramètres de localisation" + "La messagerie par satellite est accessible" + "Vous pouvez envoyer des messages par satellite s\'il n\'y a pas de réseau mobile ou Wi-Fi. Messages de Google doit être votre appli de messagerie par défaut." + "La messagerie par satellite n\'est pas prise en charge" + "La messagerie par satellite n\'est pas prise en charge sur cet appareil" + "La messagerie par satellite n\'est pas configurée" + "Assurez-vous que vous êtes connecté à Internet et réessayez d\'effectuer la configuration" + "La messagerie par satellite n\'est pas accessible" + "La messagerie par satellite n\'est pas accessible dans ce pays ou cette région" + "La messagerie par satellite n\'est pas configurée" + "Pour envoyer des messages par satellite, définissez Messages de Google comme appli de messagerie par défaut" + "La messagerie par satellite n\'est pas accessible" + "Pour vérifier si la messagerie par satellite est accessible dans ce pays ou cette région, activez les paramètres de localisation" "Configurer le Déverrouillage par empreinte digitale à nouveau" "L\'empreinte digitale %s ne peut plus être reconnue." "Les empreintes digitales %1$s et %2$s ne peuvent plus être reconnues." diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index bed2927867c4b26e57ec4c552cdf9f975c186763..bd064d1dc61e55c21c59b0fd5a7dfa153a7de827 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -613,6 +613,10 @@ "Autoriser l\'appli à déterminer la position relative entre des appareils ultra-wideband à proximité" "interagir avec les appareils Wi-Fi à proximité" "Permet à l\'appli de déterminer la position approximative des appareils Wi‑Fi à proximité, de les afficher et de s\'y connecter" + + + + "Informations sur le service de paiement NFC préféré" "Permet à l\'application d\'obtenir des informations sur le service de paiement NFC préféré, y compris les ID d\'applications et les destinations de routage enregistrés." "contrôler la communication en champ proche" diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 532c078801001708bb2baa21900e8c1a657d2330..292a952b1d46b617f8aa2bcf6dc623d630f735ec 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -612,6 +612,10 @@ "Permite que a aplicación determine a posición relativa entre os dispositivos próximos que usen banda ultralarga" "interactuar con dispositivos wifi próximos" "Permítelle á aplicación enviar anuncios e conectarse a dispositivos wifi próximos, e determinar a súa posición relativa" + + + + "Información do servizo de pagos de NFC preferido" "Permite que a aplicación obteña información do servizo de pagos de NFC preferido, como as axudas rexistradas e o destino da ruta." "controlar Near Field Communication" diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 7e36c616e81602d70ee0137d8d0c00dcd54f1669..70454a307791117c4528b6179c92ae774fbedf5e 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -612,6 +612,10 @@ "ઍપને નજીકના અલ્ટ્રા-વાઇડબૅન્ડ ડિવાઇસની વચ્ચેનું સંબંધિત અંતર નક્કી કરવાની મંજૂરી આપો" "નજીકના વાઇ-ફાઇ ડિવાઇસ સાથે ક્રિયાપ્રતિક્રિયા કરો" "ઍપને નજીકના વાઇ-ફાઇ ડિવાઇસની માહિતી બતાવવાની, તેની સાથે કનેક્ટ કરવાની અને તેની સંબંધિત સ્થિતિ નક્કી કરવાની મંજૂરી આપો" + + + + "પસંદગીની NFC ચુકવણીની સેવા વિશે માહિતી" "આ મંજૂરીને આપવાથી, ઍપ તમારી પસંદગીની NFC ચુકવણીની સેવા વિશે માહિતી મેળવી શકે છે, જેમ કે રજિસ્ટર થયેલી સહાય અને નિર્ધારિત સ્થાન." "નિઅર ફીલ્ડ કમ્યુનિકેશન નિયંત્રિત કરો" @@ -1663,7 +1667,7 @@ "હેડફોન" "USB" "સિસ્ટમ" - "બ્લૂટૂથ ઑડિઓ" + "બ્લૂટૂથ ઑડિયો" "વાયરલેસ ડિસ્પ્લે" "કાસ્ટ કરો" "ઉપકરણ સાથે કનેક્ટ કરો" @@ -2058,7 +2062,7 @@ "કોન્ફરન્સ કૉલ" "ટૂલટિપ" "રમતો" - "સંગીત અને ઑડિયો" + "મ્યુઝિક અને ઑડિયો" "મૂવી અને વીડિઓ" "ફોટો અને છબીઓ" "સામાજિક અને સંચાર" diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 38e79b1988dc30b5c2c9de3f2b452dc61ee836e7..5422f1befc40224180afb9be1f732ba59c34bdf7 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -612,6 +612,10 @@ "ऐप्लिकेशन को आस-पास मौजूद Ultra-Wideband डिवाइसों के बीच की दूरी का पता लगाने की अनुमति दें" "आस-पास मौजूद वाई-फ़ाई डिवाइसों से इंटरैक्ट करें" "इससे, ऐप्लिकेशन आस-पास मौजूद वाई-फ़ाई डिवाइसों की जानकारी दिखा पाएगा, उनसे कनेक्ट कर पाएगा, और उनकी दूरी पता लगा पाएगा" + + + + "NFC का इस्तेमाल करने वाली पैसे चुकाने की पसंदीदा सेवा की जानकारी" "अगर ऐप्लिकेशन को अनुमति दी जाती है, तो वह पैसे चुकाने की आपकी उस पसंदीदा सेवा के बारे में जानकारी पा सकता है जो NFC का इस्तेमाल करती है. इसमें रजिस्टर किए गए डिवाइस और उनके आउटपुट के रूट जैसी जानकारी शामिल होती है." "नियर फ़ील्‍ड कम्‍यूनिकेशन नियंत्रित करें" @@ -2431,54 +2435,30 @@ "चालू करें" "रद्द करें" "प्रोसेस जारी है..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "सैटलाइट एसओएस की सुविधा अब उपलब्ध है" + "मोबाइल या वाई-फ़ाई नेटवर्क न होने पर भी, आपातकालीन सेवाओं को मैसेज भेजा जा सकता है. इसके लिए, आपको Google Messages को अपने डिवाइस के डिफ़ॉल्ट मैसेजिंग ऐप्लिकेशन के तौर पर सेट करना होगा." + "सैटलाइट एसओएस की सुविधा उपलब्ध नहीं है" + "इस डिवाइस पर सैटलाइट एसओएस की सुविधा काम नहीं करती" + "सैटलाइट एसओएस सेट अप नहीं किया गया है" + "पक्का करें कि आपका डिवाइस, इंटरनेट से कनेक्ट है या नहीं. इसके बाद, फिर से सेटअप करने की कोशिश करें" + "सैटलाइट एसओएस की सुविधा उपलब्ध नहीं है" + "इस देश या इलाके में सैटलाइट एसओएस की सुविधा उपलब्ध नहीं है" + "सैटलाइट एसओएस की सुविधा सेट अप नहीं की गई" + "सैटलाइट से मैसेज भेजने के लिए, Google Messages को डिफ़ॉल्ट मैसेजिंग ऐप्लिकेशन के तौर पर सेट करें" + "सैटलाइट एसओएस की सुविधा उपलब्ध नहीं है" + "जगह की जानकारी की सेटिंग चालू करें. इससे यह देखा जा सकता है कि इस देश या इलाके में सैटलाइट एसओएस की सुविधा उपलब्ध है या नहीं" + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा उपलब्ध है" + "मोबाइल या वाई-फ़ाई नेटवर्क न होने पर भी, सैटलाइट के ज़रिए मैसेज भेजा जा सकता है. इसके लिए, आपको Google Messages को अपने डिवाइस के डिफ़ॉल्ट मैसेजिंग ऐप्लिकेशन के तौर पर सेट करना होगा." + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा काम नहीं करती" + "इस डिवाइस पर सैटलाइट के ज़रिए मैसेज भेजने की सुविधा काम नहीं करती" + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा सेट अप नहीं की गई" + "पक्का करें कि आपका डिवाइस, इंटरनेट से कनेक्ट है या नहीं. इसके बाद, फिर से सेटअप करने की कोशिश करें" + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा उपलब्ध नहीं है" + "इस देश या इलाके में सैटलाइट के ज़रिए मैसेज भेजने की सुविधा उपलब्ध नहीं है" + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा सेट अप नहीं की गई" + "सैटलाइट से मैसेज भेजने के लिए, Google Messages को डिफ़ॉल्ट मैसेजिंग ऐप्लिकेशन के तौर पर सेट करें" + "सैटलाइट के ज़रिए मैसेज भेजने की सुविधा उपलब्ध नहीं है" + "जगह की जानकारी की सेटिंग चालू करें. इससे यह देखा जा सकता है कि इस देश या इलाके में सैटलाइट के ज़रिए मैसेज भेजने की सुविधा उपलब्ध है या नहीं" "फ़िंगरप्रिंट अनलॉक की सुविधा दोबारा सेट अप करें" "अब %s की पहचान नहीं की जा सकती." "अब %1$s और %2$s की पहचान नहीं की जा सकती." diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 90e228e65afaa0fc97678d2989de7d049da9cc90..0594788fa9ceada00faac651661ca9d2ce2b037f 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -613,6 +613,10 @@ "Dopušta aplikaciji da odredi približni položaj između uređaja u blizini koji upotrebljavaju ultraširokopojasno povezivanje" "interakcija s Wi-Fi uređajima u blizini" "Aplikaciji omogućuje oglašavanje, povezivanje i određivanje približnog položaja Wi-Fi uređaja u blizini" + + + + "Informacije o preferiranoj usluzi plaćanja NFC" "Omogućuje aplikaciji primanje informacija o preferiranoj usluzi plaćanja NFC kao što su registrirana pomagala i odredište." "upravljanje beskontaktnom komunikacijom (NFC)" @@ -2432,54 +2436,30 @@ "Uključi" "Natrag" "Na čekanju..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS putem satelita sad je dostupan" + "Možete slati poruke hitnim službama ako mobilna ili Wi-Fi mreža nije dostupna. Google poruke moraju biti vaša zadana aplikacija za slanje poruka." + "SOS putem satelita nije podržan" + "SOS putem satelita nije podržan na ovom uređaju" + "SOS putem satelita nije postavljen" + "Provjerite jeste li povezani s internetom i pokušajte ponovo s postavljanjem" + "SOS putem satelita nije dostupan" + "SOS putem satelita nije dostupan u ovoj državi ili regiji" + "SOS putem satelita nije postavljen" + "Da biste slali poruke putem satelita, postavite Google poruke kao zadanu aplikaciju za slanje poruka" + "SOS putem satelita nije dostupan" + "Da biste provjerili je li SOS putem satelita dostupan u ovoj državi ili regiji, uključite postavke lokacije" + "Slanje poruka putem satelita je dostupno" + "Možete slati poruke putem satelita ako mobilna ili Wi-Fi mreža nije dostupna. Google poruke moraju biti vaša zadana aplikacija za slanje poruka." + "Slanje poruka putem satelita nije podržano" + "Slanje poruka putem satelita nije podržano na ovom uređaju" + "Slanje poruka putem satelita nije postavljeno" + "Provjerite jeste li povezani s internetom i pokušajte ponovo s postavljanjem" + "Slanje poruka putem satelita nije dostupno" + "Slanje poruka putem satelita nije dostupno u ovoj državi ili regiji" + "Slanje poruka putem satelita nije postavljeno" + "Da biste slali poruke putem satelita, postavite Google poruke kao zadanu aplikaciju za slanje poruka" + "Slanje poruka putem satelita nije dostupno" + "Da biste provjerili je li slanje poruka putem satelita dostupno u ovoj državi ili regiji, uključite postavke lokacije" "Ponovno postavite otključavanje otiskom prsta" "%s više se ne prepoznaje." "%1$s i %2$s više se ne prepoznaju." diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index d5a28f8e21983ceeaf61276a0d6c04c1edbfd43b..8adb234830aac646c9735894c4e47385a3af1f41 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -612,6 +612,10 @@ "Az alkalmazás meghatározhatja a közeli, ultraszélessávú eszközök közötti relatív pozíciót" "műveletek végrehajtása a közeli Wi‑Fi-eszközökkel" "Engedélyezi az alkalmazás számára, hogy közzétegye és meghatározza a közeli Wi-Fi-eszközök viszonylagos helyzetét, és csatlakozzon hozzájuk." + + + + "Preferált NFC fizetési szolgáltatási információk" "Lehetővé teszi az alkalmazás számára preferált NFC fizetési szolgáltatási információk (pl. regisztrált alkalmazásazonosítók és útvonali cél) lekérését." "NFC technológia vezérlése" diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 14534a7ca2a8187e641cb59b1d859c66dc9577c2..4a16d2f37194c2c2e568af48c8f271298277f652 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -612,6 +612,10 @@ "Թույլատրել հավելվածին որոշել գերլայնաշերտ կապի տեխնոլոգիան աջակցող մոտակա սարքերի միջև հարաբերական դիրքավորումը" "փոխներգործել մոտակա Wi‑Fi սարքերի հետ" "Թույլ է տալիս հավելվածին տվյալներ փոխանցել մոտակա Wi‑Fi սարքերին, միանալ դրանց և որոշել դրանց մոտավոր դիրքը։" + + + + "Տեղեկություններ NFC վճարային ծառայության մասին" "Թույլ է տալիս հավելվածին ստանալ նախընտրելի NFC վճարային ծառայության մասին տեղեկություններ (օր․՝ գրանցված լրացուցիչ սարքերի և երթուղու նպատակակետի մասին տվյալներ)։" "վերահսկել Մոտ Տարածությամբ Հաղորդակցումը" diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 360102f13139dd5308555d23c4a044b4ea6c8b0f..e90137fd761f6645aec9c663dba0338aa83cc93d 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -612,6 +612,10 @@ "Mengizinkan aplikasi menentukan posisi relatif antar-perangkat Ultra-Wideband di sekitar" "berinteraksi dengan perangkat Wi-Fi di sekitar" "Mengizinkan aplikasi menampilkan, menghubungkan, dan menentukan posisi relatif perangkat Wi-Fi di sekitar" + + + + "Informasi Layanan Pembayaran NFC Pilihan" "Mengizinkan aplikasi untuk mendapatkan informasi layanan pembayaran NFC pilihan seperti bantuan terdaftar dan tujuan rute." "kontrol NFC" diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 9d3e2e42fb119f72f77c126a4c2590514e6c472c..9ca5731ad1eb33887d3c82146b7e844d65ac52f5 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -612,6 +612,10 @@ "Leyfa forritinu að ákvarða fjarlægð milli nálægra tækja með ofurbreiðband" "eiga í samskiptum við nálæg WiFi-tæki" "Leyfir forritinu að auglýsa, tengja og áætla staðsetningu nálægra WiFi-tækja" + + + + "Upplýsingar um valda NFC-greiðsluþjónustu" "Gerir forritinu kleift að fá valda NFC-greiðsluþjónustu, svo sem skráða aðstoð og áfangastað leiðar." "stjórna nándarsamskiptum (NFC)" @@ -2431,54 +2435,30 @@ "Kveikja" "Til baka" "Í bið…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Nú er gervihnattar-SOS tiltækt" + "Þú getur sent neyðarþjónustu skilaboð, jafnvel þótt farsímakerfi eða WiFi-tenging sé ekki til staðar. Google Messages verður að vera stillt sem sjálfgefið skilaboðaforrit." + "Gervihnattar-SOS er ekki stutt" + "Gervihnattar-SOS er ekki stutt í þessu tæki" + "Gervihnattar-SOS er ekki uppsett" + "Gakktu úr skugga um að þú sért með nettengingu og reyndu uppsetningu aftur" + "Gervihnattar-SOS er ekki tiltækt" + "Gervihnattar-SOS er ekki tiltækt í þessu landi eða landsvæði" + "Gervihnattar-SOS er ekki uppsett" + "Til að senda skilaboð í gegnum gervihnött skaltu stilla Google Messages sem sjálfgefið skilaboðaforrit" + "Gervihnattar-SOS er ekki tiltækt" + "Til að athuga hvort gervihnattar-SOS eru tiltæk í þessu landi eða á þessu svæði skaltu kveikja á staðsetningarstillingum" + "Skilaboð í gegnum gervihnött eru tiltæk" + "Þú getur sent skilaboð um gervihnött ef það er ekkert farsímakerfi eða WiFi-net. Google Messages verður að vera stillt sem sjálfgefið skilaboðaforrit." + "Skilaboð í gegnum gervihnött eru ekki studd" + "Skilaboð í gegnum gervihnött eru ekki studd í þessu tæki" + "Skilaboð í gegnum gervihnött eru ekki uppsett" + "Gakktu úr skugga um að þú sért með nettengingu og reyndu uppsetningu aftur" + "Skilaboð í gegnum gervihnött eru ekki tiltæk" + "Skilaboð í gegnum gervihnött eru ekki tiltæk í þessu landi eða á þessu svæði" + "Skilaboð í gegnum gervihnött eru ekki uppsett" + "Til að senda skilaboð í gegnum gervihnött skaltu stilla Google Messages sem sjálfgefið skilaboðaforrit" + "Skilaboð í gegnum gervihnött eru ekki tiltæk" + "Til að athuga hvort skilaboð í gegnum gervihnött eru tiltæk í þessu landi eða á þessu svæði skaltu kveikja á staðsetningarstillingum" "Setja upp fingrafarskenni aftur" "Ekki er hægt að bera kennsl á %s lengur." "Ekki er hægt að bera kennsl á %1$s og %2$s lengur." @@ -2488,7 +2468,7 @@ "Ekki er hægt að bera kennsl á andlitslíkanið þitt lengur. Settu upp andlitskenni aftur." "Setja upp" "Ekki núna" - "Viðvörun fyrir: %s" + "Viðvörun: %s" "Skipta um notanda" "Þagga" "Ýttu til að þagga hljóð" diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index ee7f08db3e51b90b5056d59ebd1293d8953ccd99..ac0e4580b7a051c357597083874a14476be3dc8a 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -613,6 +613,10 @@ "Consenti all\'app di stabilire la posizione relativa tra dispositivi a banda ultralarga nelle vicinanze" "Interazione con dispositivi Wi-Fi vicini" "Consente all\'app di trasmettere annunci e connettersi a dispositivi Wi‑Fi vicini e di stabilirne la posizione relativa." + + + + "Informazioni del servizio di pagamento NFC preferito" "Consente all\'app di recuperare informazioni del servizio di pagamento NFC preferito, quali destinazione della route e identificatori applicazione registrati." "controllo Near Field Communication" @@ -2489,7 +2493,7 @@ "Il tuo modello del volto non può più essere riconosciuto. Riconfigura lo Sblocco con il Volto." "Configura" "Non ora" - "Sveglia per: %s" + "Sveglia per %s" "Cambia utente" "Disattiva audio" "Tocca per disattivare l\'audio" diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index e3f2a7cab5ebce99cdf3e6645cc74024394903b2..386f5318f53a0dac7fe6c1df70f80253ccdedd5c 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -613,6 +613,10 @@ "‏האפליקציה תזהה את המיקום היחסי בין מכשירים קרובים שמשדרים בטכנולוגיית Ultra Wideband ‏(UWB)" "‏אינטראקציה עם מכשירי Wi-Fi בקרבת מקום" "‏האפליקציה תוכל לפרסם במכשירי Wi-Fi בקרבת מקום, להתחבר אליהם ולהעריך את המיקום היחסי שלהם" + + + + "‏פרטים על שירות תשלום מועדף ב-NFC" "‏מאפשרת לאפליקציה לקבל פרטים על שירות תשלום מועדף ב-NFC, כמו עזרים רשומים ויעד של נתיב." "שליטה בתקשורת מטווח קצר" @@ -2432,54 +2436,30 @@ "הפעלה" "חזרה" "בהמתנה..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "התכונה \"תקשורת לוויינית למצב חירום\" זמינה עכשיו" + "‏אפשר לשלוח הודעות לשירותי החירום כשאין חיבור לרשת סלולרית או לרשת Wi-Fi. חובה להגדיר את Google Messages כאפליקציית ברירת המחדל להודעות." + "התכונה \"תקשורת לוויינית למצב חירום\" לא נתמכת" + "המכשיר הזה לא תומך בתכונה \"תקשורת לוויינית למצב חירום\"" + "התכונה \"תקשורת לוויינית למצב חירום\" לא מוגדרת" + "צריך לוודא שיש חיבור לאינטרנט ולנסות שוב את תהליך ההגדרה" + "התכונה \"תקשורת לוויינית למצב חירום\" לא זמינה" + "התכונה \"תקשורת לוויינית למצב חירום\" לא זמינה במדינה הזו או באזור הזה" + "התכונה \"תקשורת לוויינית למצב חירום\" לא מוגדרת" + "‏כדי להעביר הודעות באמצעות לוויין, צריך להגדיר את Google Messages כאפליקציית ברירת המחדל להודעות" + "התכונה \"תקשורת לוויינית למצב חירום\" לא זמינה" + "כדי לבדוק אם התכונה \"תקשורת לוויינית למצב חירום\" זמינה במדינה או באזור שלך, צריך להפעיל את הגדרות המיקום" + "התכונה \"העברת הודעות באמצעות לוויין\" זמינה" + "‏כשאין חיבור לרשת סלולרית או לרשת Wi-Fi, אפשר להעביר הודעות בתקשורת לוויינית. חובה להגדיר את Google Messages כאפליקציית ברירת המחדל להודעות." + "התכונה \"העברת הודעות באמצעות לוויין\" לא נתמכת" + "המכשיר הזה לא תומך בתכונה \"העברת הודעות באמצעות לוויין\"" + "התכונה \"העברת הודעות באמצעות לוויין\" לא מוגדרת" + "צריך לוודא שיש חיבור לאינטרנט ולנסות שוב את תהליך ההגדרה" + "התכונה \"העברת הודעות באמצעות לוויין\" לא זמינה" + "התכונה \"העברת הודעות באמצעות לוויין\" לא זמינה במדינה הזו או באזור הזה" + "התכונה \"העברת הודעות באמצעות לוויין\" לא מוגדרת" + "‏כדי להעביר הודעות באמצעות לוויין, צריך להגדיר את Google Messages כאפליקציית ברירת המחדל להודעות" + "התכונה \"העברת הודעות באמצעות לוויין\" לא זמינה" + "כדי לבדוק אם התכונה \"העברת הודעות באמצעות לוויין\" זמינה במדינה או באזור שלך, צריך להפעיל את הגדרות המיקום" "הגדרה חוזרת של \'ביטול הנעילה בטביעת אצבע\'" "כבר לא ניתן לזהות את %s." "כבר לא ניתן לזהות את %1$s ואת %2$s." diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 0d3a80c9b69a6f174d1827f1e8ae3f17ce137cf2..339144611afd0229da833719fbe3273f38340b5c 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -612,6 +612,10 @@ "付近の Ultra Wideband デバイス間の相対位置の特定をアプリに許可します" "付近の Wi-Fi デバイスとの通信" "付近の Wi-Fi デバイスについて、情報の表示、接続、相対位置の確認をアプリに許可します" + + + + "優先される NFC お支払いサービスの情報" "登録されている支援やルートの目的地など、優先される NFC お支払いサービスの情報を取得することをアプリに許可します。" "NFCの管理" @@ -2431,54 +2435,30 @@ "ON にする" "戻る" "保留中..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "衛星 SOS を利用できるようになりました" + "モバイル ネットワークや Wi-Fi ネットワークがなくても、緊急サービスにメッセージを送信できます。Google メッセージがデフォルトのメッセージ アプリに設定されている必要があります。" + "衛星 SOS に対応していません" + "このデバイスは衛星 SOS に対応していません" + "衛星 SOS が設定されていません" + "インターネットに接続していることを確認してから、もう一度設定してみてください" + "衛星 SOS を利用できません" + "この国または地域では衛星 SOS を利用できません" + "衛星 SOS が設定されていません" + "衛星経由でメッセージを送受信するには、Google メッセージをデフォルトのメッセージ アプリに設定してください" + "衛星 SOS を利用できません" + "この国または地域で衛星 SOS を利用できるかどうかを確認するには、位置情報の設定を ON にしてください" + "衛星通信メッセージを利用できます" + "モバイル ネットワークや Wi-Fi ネットワークがなくても、衛星経由でメッセージを送受信できます。Google メッセージがデフォルトのメッセージ アプリに設定されている必要があります。" + "衛星通信メッセージに対応していません" + "このデバイスは衛星通信メッセージに対応していません" + "衛星通信メッセージが設定されていません" + "インターネットに接続していることを確認してから、もう一度設定してみてください" + "衛星通信メッセージを利用できません" + "この国または地域では衛星通信メッセージを利用できません" + "衛星通信メッセージが設定されていません" + "衛星経由でメッセージを送受信するには、Google メッセージをデフォルトのメッセージ アプリに設定してください" + "衛星通信メッセージを利用できません" + "この国または地域で衛星通信メッセージを利用できるかどうかを確認するには、位置情報の設定を ON にしてください" "指紋認証をもう一度設定してください" "%sを認識できなくなりました。" "%1$s%2$sを認識できなくなりました。" diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 19177d65a991d7deccd642a429e113b48e3e9f93..58f78901ef5e067e8d2f37b31129a6963c0517ea 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -612,6 +612,10 @@ "ნებას რთავს აპს, დაადგინოს შედარებითი პოზიცია ახლომახლო ულტრაფართო სიხშირის მოწყობილობების შესახებ" "ინტერაქცია ახლომახლო Wi-Fi მოწყობილობებთან" "საშუალებას აძლევს აპს, განაცხადოს ახლომახლო Wi-Fi მოწყობილობების შესახებ, დაუკავშირდეს მათ და განსაზღვროს მათი შედარებითი პოზიცია" + + + + "უპირატესი NFC გადახდის სერვისის ინფორმაცია" "საშუალებას აძლევს აპს, მიიღოს უპირატესი NFC გადახდის სერვისის ინფორმაცია, მაგალითად, რეგისტრირებული დახმარება და დანიშნულება." "ახლო მოქმედების რადიოკავშირი (NFC) მართვა" @@ -2431,54 +2435,30 @@ "ჩართვა" "უკან დაბრუნება" "მომლოდინე..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "სატელიტური SOS ახლა ხელმისაწვდომია" + "შეგიძლიათ, შეტყობინება გაუგზავნოთ გადაუდებელი დახმარების სამსახურებს, თუ მობილური ან Wi-Fi ქსელი არ არის ხელმისაწვდომი. საჭიროა, რომ Google Messages იყოს თქვენი ნაგულისხმევი შეტყობინებების აპი." + "სატელიტური SOS არ არის მხარდაჭერილი" + "სატელიტური SOS არ არის მხარდაჭერილი ამ მოწყობილობაზე" + "სატელიტური SOS არ არის დაყენებული" + "დარწმუნდით, რომ დაკავშირებული ხართ ინტერნეტთან და ცადეთ ხელახლა დაყენება" + "სატელიტური SOS არ არის ხელმისაწვდომი" + "სატელიტური SOS ამ ქვეყანასა თუ რეგიონში არ არის ხელმისაწვდომი" + "სატელიტური SOS არ არის დაყენებული" + "შეტყობინებების სატელიტური მიმოცვლისთვის თქვენს ნაგულისხმევ შეტყობინებების აპად დააყენეთ Google Messages" + "სატელიტური SOS არ არის ხელმისაწვდომი" + "ამ ქვეყანაში ან რეგიონში სატელიტური SOS-ის ხელმისაწვდომობის შესამოწმებლად ჩართეთ მდებარეობის პარამეტრები" + "შეტყობინებების სატელიტური მიმოცვლა ხელმისაწვდომია" + "შეტყობინების გაგზავნა სატელიტის საშუალებით შეგიძლიათ, თუ მობილურზე ან Wi-Fi ქსელზე წვდომა არ გაქვთ. საჭიროა, რომ Google Messages იყოს თქვენი ნაგულისხმევი შეტყობინებების აპი." + "შეტყობინებების სატელიტური მიმოცვლა მხარდაჭერილი არ არის" + "ამ მოწყობილობაზე შეტყობინებების სატელიტური მიმოცვლა არ არის მხარდაჭერილი" + "შეტყობინებების სატელიტური მიმოცვლა დაყენებული არ არის" + "დარწმუნდით, რომ დაკავშირებული ხართ ინტერნეტთან და ცადეთ ხელახლა დაყენება" + "შეტყობინებების სატელიტური მიმოცვლა მიუწვდომელია" + "ამ ქვეყანაში ან რეგიონში შეტყობინებების სატელიტური მიმოცვლა მიუწვდომელია" + "შეტყობინებების სატელიტური მიმოცვლა დაყენებული არ არის" + "შეტყობინებების სატელიტური მიმოცვლისთვის თქვენს ნაგულისხმევ შეტყობინებების აპად დააყენეთ Google Messages" + "შეტყობინებების სატელიტური მიმოცვლა მიუწვდომელია" + "ამ ქვეყანაში ან რეგიონში შეტყობინებების სატელიტური მიმოცვლის ხელმისაწვდომობის შესამოწმებლად ჩართეთ მდებარეობის პარამეტრები" "ანაბეჭდით განბლოკვის ხელახლა დაყენება" "%s-ის ამოცნობა ვეღარ ხერხდება." "%1$s-ისა და %2$s-ის ამოცნობა ვეღარ ხერხდება." diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 70774d6cafe14bbaca3cf6b6fabf22d9e89ca8b4..737f950567a1f12c035e31e6a33e74e9a88e0146 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -612,6 +612,10 @@ "Қолданбаға маңайдағы кең жолақты құрылғылардың бір-біріне қатысты орнын анықтауға мүмкіндік береді." "маңайдағы Wi-Fi құрылғыларымен байланысу" "Қолданба маңайдағы Wi‑Fi құрылғыларына жарнама беріп, оларға қосылып, шамамен орналасқан жерін анықтай алады." + + + + "Таңдаулы NFC төлеу қызметі туралы ақпарат" "Қолданба тіркелген көмектер және баратын жер маршруты сияқты таңдаулы NFC төлеу қызметі туралы ақпаратты ала алатын болады." "NFC функциясын басқару" diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index c758e0501aa87cd9a9c987eade16c0e97c6f0e07..0ecad8f60fe15d8d41b9f00b42f2c07c8f44bbd4 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -612,6 +612,10 @@ "អនុញ្ញាតឱ្យ​កម្មវិធី​កំណត់ចម្ងាយ​ពាក់ព័ន្ធ​រវាងឧបករណ៍ Ultra-Wideband ដែលនៅជិត" "ធ្វើអន្តរកម្ម​ជាមួយឧបករណ៍ Wi‑Fi ដែលនៅជិត" "អនុញ្ញាតឱ្យ​កម្មវិធី​ផ្សាយពាណិជ្ជកម្ម ភ្ជាប់ និងកំណត់ទីតាំង​ពាក់ព័ន្ធរបស់​ឧបករណ៍ Wi‑Fi ដែលនៅជិត" + + + + "ព័ត៌មានអំពី​សេវាបង់ប្រាក់តាម NFC ជាអាទិភាព" "អនុញ្ញាតឱ្យ​កម្មវិធី​ទទួលបាន​ព័ត៌មានអំពី​សេវាបង់ប្រាក់តាម nfc ជាអាទិភាព​ដូចជា គោលដៅផ្លូវ និង​ព័ត៌មាន​កំណត់អត្តសញ្ញាណ​កម្មវិធី ដែលបានចុះឈ្មោះ​ជាដើម។" "ពិនិត្យ​ការ​ទាក់ទង​នៅ​ក្បែរ (NFC)" @@ -2431,54 +2435,30 @@ "បើក" "ថយ​ក្រោយ" "កំពុងរង់ចាំ..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "ឥឡូវនេះ អាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបបានហើយ" + "អ្នកអាចផ្ញើសារទៅសេវាសង្គ្រោះបន្ទាន់ ប្រសិនបើមិនមានបណ្ដាញឧបករណ៍​ចល័ត ឬ Wi-Fi ទេ។ Google Messages ត្រូវតែជាកម្មវិធី​ផ្ញើសារលំនាំដើមរបស់អ្នក។" + "មិនអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបបានទេ" + "មិនអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបនៅលើឧបករណ៍នេះបានទេ" + "ការប្រកាសអាសន្នតាមផ្កាយរណបមិនត្រូវបានរៀបចំទេ" + "សូមប្រាកដថា អ្នកបានភ្ជាប់អ៊ីនធឺណិត រួចសាកល្បងរៀបចំម្ដងទៀត" + "មិនអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបបានទេ" + "មិនអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណនៅក្នុងប្រទេស ឬតំបន់នេះបានទេ" + "មិនបានរៀបចំការប្រកាសអាសន្នតាមផ្កាយរណបទេ" + "ដើម្បីផ្ញើសារតាមផ្កាយរណប សូមកំណត់ Google Messages ជាកម្មវិធីផ្ញើសារលំនាំដើមរបស់អ្នក" + "មិនអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបបានទេ" + "ដើម្បីពិនិត្យមើលថាតើអាចប្រើការប្រកាសអាសន្នតាមផ្កាយរណបនៅក្នុងប្រទេស ឬតំបន់នេះបានឬអត់ សូមបើកការ​កំណត់​ទីតាំង​" + "អាចផ្ញើ​សារតាមផ្កាយរណបបាន" + "អ្នកអាចផ្ញើសារតាមផ្កាយរណប ប្រសិនបើមិនមានបណ្ដាញឧបករណ៍​ចល័ត ឬ Wi-Fi ទេ។ Google Messages ត្រូវតែជាកម្មវិធី​ផ្ញើសារលំនាំដើមរបស់អ្នក។" + "មិនអាចផ្ញើ​សារតាមផ្កាយរណបបានទេ" + "មិនអាចផ្ញើ​សារតាមផ្កាយរណបនៅលើឧបករណ៍នេះបានទេ" + "មិនបានរៀបចំការ​ផ្ញើ​សារតាមផ្កាយរណបទេ" + "សូមប្រាកដថា អ្នកបានភ្ជាប់អ៊ីនធឺណិត រួចសាកល្បងរៀបចំម្ដងទៀត" + "មិនអាចផ្ញើ​សារតាមផ្កាយរណបបានទេ" + "មិនអាចផ្ញើ​សារតាមផ្កាយរណបនៅក្នុងប្រទេស ឬតំបន់នេះបានទេ" + "មិនបានរៀបចំការ​ផ្ញើ​សារតាមផ្កាយរណបទេ" + "ដើម្បីផ្ញើសារតាមផ្កាយរណប សូមកំណត់ Google Messages ជាកម្មវិធីផ្ញើសារលំនាំដើមរបស់អ្នក" + "មិនអាចផ្ញើ​សារតាមផ្កាយរណបបានទេ" + "ដើម្បីពិនិត្យមើលថាតើអាចផ្ញើ​សារតាមផ្កាយរណបនៅក្នុងប្រទេស ឬតំបន់នេះបានឬអត់ សូមបើកការ​កំណត់​ទីតាំង​" "រៀបចំការដោះសោ​ដោយស្កេន​ស្នាមម្រាមដៃម្ដងទៀត" "លែងអាចសម្គាល់ %s បានទៀតហើយ។" "លែងអាចសម្គាល់ %1$s និង %2$s បានទៀតហើយ។" diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 8efcd08693031aa8af74e46e2a22fc35cf0ddb5a..c64f6ec1e7a70f4b48dc054766a4f42bb7c600a1 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -612,6 +612,10 @@ "ಸಮೀಪದಲ್ಲಿರುವ ಅಲ್ಟ್ರಾ-ವೈಡ್‌ಬ್ಯಾಂಡ್ ಸಾಧನಗಳ ನಡುವೆ ಸಂಬಂಧಿತ ಸ್ಥಾನವನ್ನು ನಿರ್ಧರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಿ" "ಹತ್ತಿರದ ವೈ -ಫೈ ಸಾಧನಗಳ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಿ" "ಹತ್ತಿರದ ವೈ -ಫೈ ಸಾಧನಗಳ ಸಂಬಂಧಿತ ಸ್ಥಾನವನ್ನು ಸೂಚಿಸಲು, ಕನೆಕ್ಟ್ ಮಾಡಲು ಮತ್ತು ನಿರ್ಧರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ" + + + + "ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವಾ ಮಾಹಿತಿ" "ನೋಂದಾಯಿತ ಆ್ಯಪ್ ಗುರುತಿಸುವಿಕೆಗಳು ಮತ್ತು ಮಾರ್ಗ ಗಮ್ಯಸ್ಥಾನಗಳಂತಹ ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವೆಗಳ ಬಗ್ಗೆ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ." "ಸಮೀಪ ಕ್ಷೇತ್ರ ಸಂವಹನವನ್ನು ನಿಯಂತ್ರಿಸಿ" @@ -2431,54 +2435,30 @@ "ಆನ್ ಮಾಡಿ" "ಹಿಂದಿರುಗಿ" "ಬಾಕಿ ಉಳಿದಿದೆ..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "ಸ್ಯಾಟಲೈಟ್‌ SOS ಈಗ ಲಭ್ಯವಿದೆ" + "ಮೊಬೈಲ್ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್ ಇಲ್ಲದಿದ್ದರೆ ನೀವು ತುರ್ತು ಸೇವೆಗಳಿಗೆ ಸಂದೇಶ ಕಳುಹಿಸಬಹುದು. Google Messages ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಆ್ಯಪ್‌ ಆಗಿರಬೇಕು." + "ಸ್ಯಾಟಲೈಟ್‌ SOS ಬೆಂಬಲಿತವಾಗಿಲ್ಲ" + "ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್ SOS ಬೆಂಬಲಿತವಾಗಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್ SOS ಅನ್ನು ಸೆಟಪ್‌ ಮಾಡಲಾಗಿಲ್ಲ" + "ನೀವು ಇಂಟರ್ನೆಟ್‌ಗೆ ಕನೆಕ್ಟ್ ಆಗಿರುವಿರಿ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ಪುನಃ ಸೆಟಪ್‌ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಿ" + "ಸ್ಯಾಟಲೈಟ್ SOS ಲಭ್ಯವಿಲ್ಲ" + "ಈ ದೇಶ ಅಥವಾ ಪ್ರದೇಶದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್‌ SOS ಲಭ್ಯವಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್‌ SOS ಸೆಟಪ್‌ ಆಗಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್‌ ಮೂಲಕ ಸಂದೇಶ ಕಳುಹಿಸಲು, Google Messages ಅನ್ನು ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಮೆಸೇಜಿಂಗ್ ಆ್ಯಪ್‌ನಂತೆ ಸೆಟ್‌ ಮಾಡಿ" + "ಸ್ಯಾಟಲೈಟ್ SOS ಲಭ್ಯವಿಲ್ಲ" + "ಈ ದೇಶ ಅಥವಾ ಪ್ರದೇಶದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್ SOS ಲಭ್ಯವಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಲು, ಸ್ಥಳ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆನ್‌ ಮಾಡಿ" + "ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಲಭ್ಯವಿದೆ" + "ಮೊಬೈಲ್‌ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್ ಇಲ್ಲದಿದ್ದರೆ ನೀವು ಸ್ಯಾಟಲೈಟ್ ಮೂಲಕ ಸಂದೇಶ ಕಳುಹಿಸಬಹುದು. Google Messages ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಆ್ಯಪ್‌ ಆಗಿರಬೇಕು." + "ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಗೆ ಬೆಂಬಲವಿಲ್ಲ" + "ಈ ಸಾಧನದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್ ಮೆಸೇಜಿಂಗ್‌ ಸೆಟಪ್‌ ಮಾಡಲಾಗಿಲ್ಲ" + "ನೀವು ಇಂಟರ್ನೆಟ್‌ಗೆ ಕನೆಕ್ಟ್ ಆಗಿರುವಿರಿ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ಪುನಃ ಸೆಟಪ್‌ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಿ" + "ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಕಳುಹಿಸುವಿಕೆ ಲಭ್ಯವಿಲ್ಲ" + "ಈ ದೇಶ ಅಥವಾ ಪ್ರದೇಶದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಲಭ್ಯವಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್ ಮೆಸೇಜಿಂಗ್‌ ಸೆಟಪ್‌ ಮಾಡಲಾಗಿಲ್ಲ" + "ಸ್ಯಾಟಲೈಟ್‌ ಮೂಲಕ ಸಂದೇಶ ಕಳುಹಿಸಲು, Google Messages ಅನ್ನು ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಮೆಸೇಜಿಂಗ್ ಆ್ಯಪ್‌ನಂತೆ ಸೆಟ್‌ ಮಾಡಿ" + "ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಕಳುಹಿಸುವಿಕೆ ಲಭ್ಯವಿಲ್ಲ" + "ಈ ದೇಶ ಅಥವಾ ಪ್ರದೇಶದಲ್ಲಿ ಸ್ಯಾಟಲೈಟ್‌ ಮೆಸೇಜಿಂಗ್‌ ಲಭ್ಯವಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಲು, ಸ್ಥಳ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆನ್‌ ಮಾಡಿ" "ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಅನ್‌ಲಾಕ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಸೆಟಪ್ ಮಾಡಿ" "%s ಅನ್ನು ಇನ್ನು ಮುಂದೆ ಗುರುತಿಸಲಾಗುವುದಿಲ್ಲ." "%1$s ಮತ್ತು %2$s ಅನ್ನು ಇನ್ನು ಮುಂದೆ ಗುರುತಿಸಲಾಗುವುದಿಲ್ಲ." diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index a069805148f519a3274d73725c653b41f9be7175..d63d43e4cddd5fe02f7a7616159e9790aeeb5c72 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -612,6 +612,10 @@ "앱이 근처의 초광대역 기기 간 상대적 위치를 파악하도록 허용" "근처 Wi‑Fi 기기와 상호작용" "앱이 광역 신호를 보내 근처에 있는 Wi‑Fi 기기의 상대적인 위치를 확인하고 연결할 수 있도록 허용합니다." + + + + "기본 NFC 결제 서비스 정보" "앱이 등록된 AID와 경로 목적지 같은 기본 NFC 결제 서비스 정보를 확인하도록 허용합니다." "NFC(Near Field Communication) 제어" @@ -2431,54 +2435,30 @@ "사용" "뒤로" "대기 중…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "이제 위성 긴급 SOS를 사용할 수 있음" + "모바일 또는 Wi-Fi 네트워크가 없는 경우 응급 서비스로 메시지를 보낼 수 있습니다. Google 메시지가 기본 메시지 앱으로 설정되어 있어야 합니다." + "위성 긴급 SOS가 지원되지 않음" + "이 기기에서는 위성 긴급 SOS가 지원되지 않습니다." + "위성 긴급 SOS가 설정되지 않음" + "인터넷에 연결되어 있는지 확인한 후 다시 설정해 보세요." + "위성 긴급 SOS를 사용할 수 없음" + "이 국가 또는 지역에서는 위성 긴급 SOS를 사용할 수 없습니다." + "위성 긴급 SOS가 설정되지 않음" + "위성으로 메시지를 보내려면 Google 메시지를 기본 메시지 앱으로 설정하세요." + "위성 긴급 SOS를 사용할 수 없음" + "이 국가 또는 지역에서 위성 긴급 SOS를 사용할 수 있는지 확인하려면 위치 설정을 사용 설정하세요." + "위성 메시지 사용 가능" + "모바일 또는 Wi-Fi 네트워크가 없는 경우 위성으로 메시지를 보낼 수 있습니다. Google 메시지가 기본 메시지 앱으로 설정되어 있어야 합니다." + "위성 메시지가 지원되지 않음" + "이 기기에서는 위성 메시지가 지원되지 않습니다." + "위성 메시지가 설정되지 않음" + "인터넷에 연결되어 있는지 확인한 후 다시 설정해 보세요." + "위성 메시지를 사용할 수 없음" + "이 국가 또는 지역에서는 위성 메시지를 사용할 수 없습니다." + "위성 메시지가 설정되지 않음" + "위성으로 메시지를 보내려면 Google 메시지를 기본 메시지 앱으로 설정하세요." + "위성 메시지를 사용할 수 없음" + "이 국가 또는 지역에서 위성 메시지를 사용할 수 있는지 확인하려면 위치 설정을 사용 설정하세요." "지문 잠금 해제 다시 설정" "%s 지문을 더 이상 인식할 수 없습니다." "%1$s%2$s 지문을 더 이상 인식할 수 없습니다." diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index ec0059bb31f679635c4c981dd2f507429ed8730a..e09036a1b679185b3393b83cff83bd4172074d54 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -612,6 +612,10 @@ "Колдонмо кең тилкелүү тармак аркылуу туташа турган жакын жердеги түзмөктөрдү аныктай алат" "Жакын жердеги Wi‑Fi түзмөктөрүнө байланышуу" "Колдонмо жакын жердеги Wi-Fi түзмөктөргө туташып, алардын жайгашкан жерин аныктап, ар кандай нерселерди өткөрө алат." + + + + "Тандалган NFC төлөм кызматы жөнүндө маалымат" "Колдонмого катталган жардам же көздөлгөн жерге маршрут сыяктуу тандалган nfc төлөм кызматы жөнүндө маалыматты алууга уруксат берүү." "Near Field Communication көзөмөлү" diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 3962f09f0bacd802ab413e562a4a4c02d1babd5d..a9ebcd76550237948481da7acce10574f2159555 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -612,6 +612,10 @@ "ອະນຸຍາດໃຫ້ແອັບກຳນົດຕຳແໜ່ງທີ່ສຳພັນກັນລະຫວ່າງອຸປະກອນ Ultra-Wideband ທີ່ຢູ່ໃກ້ຄຽງ" "ໂຕ້ຕອບກັບອຸປະກອນ Wi‑Fi ທີ່ຢູ່ໃກ້ຄຽງ" "ອະນຸຍາດໃຫ້ແອັບໂຄສະນາ, ເຊື່ອມຕໍ່ ແລະ ກຳນົດຕຳແໜ່ງສຳພັນຂອງອຸປະກອນ Wi-Fi ທີ່ຢູ່ໃກ້ຄຽງໄດ້" + + + + "ຂໍ້ມູນບໍລິການການຈ່າຍເງິນ NFC ທີ່ຕ້ອງການ" "ອະນຸຍາດໃຫ້ແອັບຮັບຂໍ້ມູນບໍລິການການຈ່າຍເງິນ NFC ທີ່ຕ້ອງການໄດ້ ເຊັ່ນ: ການຊ່ວຍເຫຼືອແບບລົງທະບຽນ ແລະ ປາຍທາງເສັ້ນທາງ." "ຄວບຄຸມ Near Field Communication" @@ -2431,54 +2435,30 @@ "ເປີດໃຊ້" "ກັບຄືນ" "ລໍຖ້າດຳເນີນການ..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "ຕອນນີ້ SOS ດາວທຽມພ້ອມໃຫ້ບໍລິການແລ້ວ" + "ທ່ານສາມາດສົ່ງຂໍ້ຄວາມໄປຫາບໍລິການສຸກເສີນໄດ້ໃນກໍລະນີທີ່ບໍ່ມີເຄືອຂ່າຍມືຖື ຫຼື Wi-Fi. ຕ້ອງຕັ້ງຄ່າ Google Messages ເປັນແອັບຮັບສົ່ງຂໍ້ຄວາມເລີ່ມຕົ້ນຂອງທ່ານ." + "ບໍ່ຮອງຮັບ SOS ດາວທຽມ" + "ອຸປະກອນນີ້ບໍ່ຮອງຮັບ SOS ດາວທຽມ" + "ບໍ່ໄດ້ຕັ້ງຄ່າ SOS ດາວທຽມ" + "ກະລຸນາກວດສອບໃຫ້ໝັ້ນໃຈວ່າທ່ານໄດ້ເຊື່ອມຕໍ່ອິນເຕີເນັດແລ້ວ ແລະ ລອງຕັ້ງຄ່າອີກຄັ້ງ" + "SOS ດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການ" + "SOS ດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການໃນປະເທດ ຫຼື ພາກພື້ນນີ້" + "ບໍ່ໄດ້ຕັ້ງຄ່າ SOS ດາວທຽມ" + "ເພື່ອຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ, ໃຫ້ຕັ້ງ Google Messages ເປັນແອັບຮັບສົ່ງຂໍ້ຄວາມເລີ່ມຕົ້ນຂອງທ່ານ" + "SOS ດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການ" + "ເພື່ອກວດສອບວ່າ SOS ດາວທຽມພ້ອມໃຫ້ບໍລິການໃນປະເທດ ຫຼື ພາກພື້ນນີ້ຫຼືບໍ່, ໃຫ້ເປີດການຕັ້ງຄ່າສະຖານທີ່" + "ການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມພ້ອມໃຫ້ບໍລິການ" + "ທ່ານສາມາດຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມໄດ້ໃນກໍລະນີທີ່ບໍ່ມີເຄືອຂ່າຍມືຖື ຫຼື Wi-Fi. ຕ້ອງຕັ້ງຄ່າ Google Messages ເປັນແອັບຮັບສົ່ງຂໍ້ຄວາມເລີ່ມຕົ້ນຂອງທ່ານ." + "ບໍ່ຮອງຮັບການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ" + "ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ" + "ບໍ່ໄດ້ຕັ້ງຄ່າການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ" + "ກະລຸນາກວດສອບໃຫ້ໝັ້ນໃຈວ່າທ່ານໄດ້ເຊື່ອມຕໍ່ອິນເຕີເນັດແລ້ວ ແລະ ລອງຕັ້ງຄ່າອີກຄັ້ງ" + "ການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການ" + "ການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການໃນປະເທດ ຫຼື ພາກພື້ນນີ້" + "ບໍ່ໄດ້ຕັ້ງຄ່າການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ" + "ເພື່ອຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມ, ໃຫ້ຕັ້ງ Google Messages ເປັນແອັບຮັບສົ່ງຂໍ້ຄວາມເລີ່ມຕົ້ນຂອງທ່ານ" + "ການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມບໍ່ພ້ອມໃຫ້ບໍລິການ" + "ເພື່ອກວດສອບວ່າການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມພ້ອມໃຫ້ບໍລິການໃນປະເທດ ຫຼື ພາກພື້ນນີ້ຫຼືບໍ່, ໃຫ້ເປີດການຕັ້ງຄ່າສະຖານທີ່" "ຕັ້ງຄ່າການປົດລັອກດ້ວຍລາຍນິ້ວມືຄືນໃໝ່" "ບໍ່ສາມາດຈຳແນກ %s ໄດ້ອີກຕໍ່ໄປ." "ບໍ່ສາມາດຈຳແນກ %1$s ແລະ %2$s ໄດ້ອີກຕໍ່ໄປ." diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index c88b6635fab1c652e66d881ffaf2328ad672ad2d..21fddd010ca4c029818ebaf9fbb81c963dbcae27 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -614,6 +614,10 @@ "Leisti programai nustatyti apytikslę netoliese esančių itin plataus dažnio juostos įrenginių poziciją" "sąveikauti su „Wi‑Fi“ įrenginiais netoliese" "Leidžiama programai reklamuoti, prisijungti ir nustatyti apytikslę netoliese esančių „Wi-Fi“ įrenginių poziciją" + + + + "Pageidaujama ARL mokėjimo paslaugos informacija" "Programai leidžiama gauti pageidaujamą ARL mokamos paslaugos informaciją, pvz., užregistruotą pagalbą ir maršrutų tikslus." "valdyti artimo lauko perdavimą (angl. „Near Field Communication“)" @@ -2433,54 +2437,30 @@ "Įjungti" "Grįžti" "Laukiama..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Prisijungimas prie palydovo kritiniu atveju nepasiekiamas" + "Galite siųsti pranešimus pagalbos tarnyboms, kai nėra mobiliojo ryšio ar „Wi-Fi“ tinklo. „Google Messages“ turi būti jūsų numatytoji pranešimų programa." + "Prisijungimo prie palydovo kritiniu atveju funkcija nepalaikoma" + "Šiame įrenginyje nepalaikoma prisijungimo prie palydovo kritiniu atveju funkcija" + "Prisijungimo prie palydovo kritiniu atveju funkcija nenustatyta" + "Įsitikinkite, kad esate prisijungę prie interneto ir bandykite nustatyti dar kartą" + "Prisijungimo prie palydovo kritiniu atveju funkcija nepasiekiama" + "Prisijungimo prie palydovo kritiniu atveju funkcija nepasiekiama šioje šalyje arba regione" + "Prisijungimo prie palydovo kritiniu atveju funkcija nenustatyta" + "Jei norite siųsti pranešimus per palydovą, nustatykite „Google Messages“ kaip numatytąją pranešimų programą" + "Prisijungimo prie palydovo kritiniu atveju funkcija nepasiekiama" + "Jei norite patikrinti, ar prisijungimo prie palydovo kritiniu atveju funkcija pasiekiama šioje šalyje ar regione, įjunkite vietovės nustatymus" + "Pasiekiama susirašinėjimo palydoviniais pranešimais paslauga" + "Galite siųsti pranešimus palydoviniu ryšiu, kai nėra mobiliojo ryšio ar „Wi-Fi“ tinklo. „Google Messages“ turi būti jūsų numatytoji pranešimų programa." + "Susirašinėjimo palydoviniais pranešimais paslauga nepalaikoma" + "Šiame įrenginyje nepalaikoma susirašinėjimo palydoviniais pranešimais paslauga" + "Susirašinėjimo palydoviniais pranešimais paslauga nenustatyta" + "Įsitikinkite, kad esate prisijungę prie interneto ir bandykite nustatyti dar kartą" + "Susirašinėjimo palydoviniais pranešimais paslauga nepasiekiama" + "Susirašinėjimo palydoviniais pranešimais paslauga nepasiekiama šioje šalyje ar regione" + "Susirašinėjimo palydoviniais pranešimais paslauga nenustatyta" + "Jei norite siųsti pranešimus per palydovą, nustatykite „Google Messages“ kaip numatytąją pranešimų programą" + "Susirašinėjimo palydoviniais pranešimais paslauga nepasiekiama" + "Jei norite patikrinti, ar susirašinėjimo palydoviniais pranešimais paslauga pasiekiama šioje šalyje ar regione, įjunkite vietovės nustatymus" "Atrakinimo piršto atspaudu nustatymas dar kartą" "%s nebegalima atpažinti." "%1$s ir %2$s nebegalima atpažinti." diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 38d1891b13e27fd8e994c59e1f819cfdeda89cc2..15ee04a5f1d72c9245ff1e6ce1f82c717782b399 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -613,6 +613,10 @@ "Atļaut lietotnei noteikt relatīvo atrašanās vietu starp tuvumā esošām ultraplatjoslas ierīcēm" "Mijiedarbība ar tuvumā esošām Wi‑Fi ierīcēm" "Atļauj lietotnei nodot datus tuvumā esošām Wi‑Fi ierīcē, izveidot savienojumu ar tām un noteikt to relatīvo pozīciju." + + + + "Informācija par vēlamo NFC maksājumu pakalpojumu" "Ļauj lietotnei iegūt informāciju par vēlamo NFC maksājumu pakalpojumu, piemēram, par reģistrētajiem lietojumprogrammu ID un maršruta galamērķi." "kontrolē tuvlauka saziņu" diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 87fd79928298b87913ae570b8498cc6d5774d500..b5187726028b746d387c39d2e1fb8b50ea7d156b 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -612,6 +612,10 @@ "Дозволува апликацијата да ја одреди релативната положба помеѓу уредите со ултраширок појас во близина" "да има интеракција со уредите со Wi‑Fi во близина" "Дозволува апликацијата да рекламира, да се поврзува и да ја одредува релативната положба на уреди со Wi‑Fi во близина" + + + + "Информации за претпочитаната услуга за плаќање преку NFC" "Дозволува апликацијата да добие информации за претпочитаната услуга за плаќање преку NFC, како регистрирани помагала и дестинација на маршрутата." "контролирај комуникација на блиско поле" diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 086ebe1fcaa5e729302c12b8308ee8634b039a32..aabeb9b986463d9f065029d28fc8ff7086bacfdc 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -612,6 +612,10 @@ "സമീപമുള്ള അൾട്രാ-വെെഡ്ബാൻഡ് ഉപകരണങ്ങൾ തമ്മിലുള്ള ആപേക്ഷിക സ്ഥാനം നിർണ്ണയിക്കാൻ ആപ്പിനെ അനുവദിക്കുക" "സമീപമുള്ള വൈഫൈ ഉപകരണങ്ങളുമായി ഇടപഴകുക" "സമീപമുള്ള വൈഫൈ ഉപകരണങ്ങൾ കാണിക്കാനും അവയിലേക്ക് കണക്റ്റ് ചെയ്യാനും അവയുടെ ആപേക്ഷിക സ്ഥാനം നിർണ്ണയിക്കാനും ആപ്പിനെ അനുവദിക്കൂ" + + + + "തിരഞ്ഞെടുത്ത NFC പേയ്‌മെന്റ് സേവനത്തെ സംബന്ധിച്ച വിവരങ്ങൾ" "റൂട്ട് ലക്ഷ്യസ്ഥാനം, രജിസ്‌റ്റർ ചെയ്തിരിക്കുന്ന സഹായങ്ങൾ എന്നിവ പോലുള്ള, തിരഞ്ഞെടുത്ത NFC പേയ്‌മെന്റ് സേവനത്തെ സംബന്ധിച്ച വിവരങ്ങൾ ലഭിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു." "സമീപ ഫീൽഡുമായുള്ള ആശയവിനിമയം നിയന്ത്രിക്കുക" @@ -2431,54 +2435,30 @@ "ഓണാക്കുക" "മടങ്ങുക" "തീർപ്പാക്കിയിട്ടില്ല..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "സാറ്റലൈറ്റ് SOS ഇപ്പോൾ ലഭ്യമാണ്" + "മൊബൈൽ അല്ലെങ്കിൽ വൈഫൈ നെറ്റ്‌വർക്ക് ലഭ്യമല്ലെങ്കിൽ നിങ്ങൾക്ക് അടിയന്തര സേവനങ്ങൾക്ക് സന്ദേശമയയ്ക്കാം. നിങ്ങളുടെ ഡിഫോൾട്ട് സന്ദേശമയയ്ക്കൽ ആപ്പ് Google Messages ആയിരിക്കണം." + "സാറ്റലൈറ്റ് SOS പിന്തുണയ്ക്കുന്നില്ല" + "ഈ ഉപകരണത്തിൽ സാറ്റലൈറ്റ് SOS പിന്തുണയ്ക്കുന്നില്ല" + "സാറ്റലൈറ്റ് SOS സജ്ജീകരിച്ചിട്ടില്ല" + "നിങ്ങൾ ഇന്റർനെറ്റിലേക്ക് കണക്റ്റ് ചെയ്‌തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കിയ ശേഷം സജ്ജീകരിക്കാൻ വീണ്ടും ശ്രമിക്കുക" + "സാറ്റലൈറ്റ് SOS ലഭ്യമല്ല" + "ഈ രാജ്യത്ത് അല്ലെങ്കിൽ പ്രദേശത്ത് സാറ്റലൈറ്റ് SOS ലഭ്യമല്ല" + "സാറ്റലൈറ്റ് SOS സജ്ജീകരിച്ചിട്ടില്ല" + "സാറ്റലൈറ്റ് വഴി സന്ദേശമയയ്ക്കാൻ, നിങ്ങളുടെ ഡിഫോൾട്ട് സന്ദേശമയയ്ക്കൽ ആപ്പായി Google Messages സജ്ജീകരിക്കുക" + "സാറ്റലൈറ്റ് SOS ലഭ്യമല്ല" + "ഈ രാജ്യത്ത് അല്ലെങ്കിൽ പ്രദേശത്ത് സാറ്റലൈറ്റ് SOS ലഭ്യമാണോയെന്ന് പരിശോധിക്കുന്നതിന്, ലൊക്കേഷൻ ക്രമീകരണം ഓണാക്കുക" + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ ലഭ്യമാണ്" + "മൊബൈൽ അല്ലെങ്കിൽ വൈഫൈ നെറ്റ്‌വർക്ക് ഇല്ലെങ്കിൽ നിങ്ങൾക്ക് സാറ്റലൈറ്റ് വഴി സന്ദേശമയയ്ക്കാം. നിങ്ങളുടെ ഡിഫോൾട്ട് സന്ദേശമയയ്ക്കൽ ആപ്പ് Google Messages ആയിരിക്കണം." + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ പിന്തുണയ്ക്കുന്നില്ല" + "ഈ ഉപകരണത്തിൽ സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ പിന്തുണയ്ക്കുന്നില്ല" + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ സജ്ജീകരിച്ചിട്ടില്ല" + "നിങ്ങൾ ഇന്റർനെറ്റിലേക്ക് കണക്റ്റ് ചെയ്‌തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കിയ ശേഷം സജ്ജീകരിക്കാൻ വീണ്ടും ശ്രമിക്കുക" + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ ലഭ്യമല്ല" + "ഈ രാജ്യത്ത് അല്ലെങ്കിൽ പ്രദേശത്ത് സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ ലഭ്യമല്ല" + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ സജ്ജീകരിച്ചിട്ടില്ല" + "സാറ്റലൈറ്റ് വഴി സന്ദേശമയയ്ക്കാൻ, നിങ്ങളുടെ ഡിഫോൾട്ട് സന്ദേശമയയ്ക്കൽ ആപ്പായി Google Messages സജ്ജീകരിക്കുക" + "സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ ലഭ്യമല്ല" + "ഈ രാജ്യത്ത് അല്ലെങ്കിൽ പ്രദേശത്ത് സാറ്റലൈറ്റ് സഹായത്തോടെ സന്ദേശമയയ്ക്കൽ ലഭ്യമാണോയെന്ന് പരിശോധിക്കാൻ, ലൊക്കേഷൻ ക്രമീകരണം ഓണാക്കുക" "ഫിംഗർപ്രിന്റ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക" "%s ഇനി തിരിച്ചറിയാനാകില്ല." "%1$s, %2$s എന്നിവ ഇനി തിരിച്ചറിയാനാകില്ല." @@ -2498,7 +2478,7 @@ "SMS" "സംഗീതം" "കലണ്ടർ" - "കാൽക്കുലേറ്റർ" + "Calculator" "Maps" "ആപ്പുകൾ" "നിങ്ങളുടെ ഫിംഗർപ്രിന്റുകൾ ഇനി തിരിച്ചറിയാനാകില്ല. ഫിംഗർപ്രിന്റ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക." diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 42db4c97ace41ba96f623642c4983cc6159ac349..c625d31dbd79ab9348e8712e0ae0d7a641d27f3f 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -612,6 +612,10 @@ "Аппад ойролцоох ультра өргөн зурвасын төхөөрөмжүүдийн хоорондох холбоотой байрлалыг тодорхойлохыг зөвшөөрөх" "ойролцоох Wi-Fi төхөөрөмжүүдтэй харилцах" "Аппад ойролцоох Wi-Fi төхөөрөмжүүдтэй холбоотой байрлалыг мэдэгдэх, холбох, тодорхойлохыг зөвшөөрнө" + + + + "Сонгосон NFC төлбөрийн үйлчилгээний мэдээлэл" "Бүртгүүлсэн төхөөрөмж болон маршрутын хүрэх цэг зэрэг сонгосон nfc төлбөрийн үйлчилгээний мэдээллийг авахыг аппад зөвшөөрдөг." "ойролцоо талбарын холбоог удирдах" diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 2cfeaa2adab91a3fef5b71fc78e1f167a6ed9b20..3240d59140562328a4d39d478423e03eb677a035 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -612,6 +612,10 @@ "ॲपला जवळच्या अल्ट्रा-वाइडबँड डिव्हाइसदरम्यानचे संबंधित स्थान निर्धारित करण्याची अनुमती द्या" "जवळपासच्या वाय-फाय डिव्हाइसशी संवाद साधा" "ॲपला जाहिरात करण्याची, कनेक्ट करण्याची आणि जवळपासच्या वाय-फाय डिव्हाइसचे संबंधित स्थान निर्धारित करण्याची परवानगी देते" + + + + "प्राधान्यकृत NFC पेमेंट सेवा माहिती" "नोंदणीकृत एड्स आणि मार्ग गंतव्यस्थान सारखी प्राधान्यकृत एनएफसी पेमेंट सेवेची माहिती मिळवण्यासाठी अ‍ॅपला अनुमती देते." "फील्ड जवळील कम्युनिकेशन नियंत्रित करा" @@ -2431,54 +2435,30 @@ "सुरू करा" "मागे जा" "प्रलंबित आहे..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "सॅटेलाइट SOS आता उपलब्ध आहे" + "कोणतेही मोबाइल किंवा वाय-फाय नेटवर्क उपलब्ध नसल्यास, तुम्ही आणीबाणी सेवांना मेसेज पाठवू शकता. Google Messages हे तुमचे डीफॉल्ट मेसेजिंग ॲप असणे आवश्यक आहे." + "सॅटेलाइट SOS ला सपोर्ट नाही" + "या डिव्हाइसवर सॅटेलाइट SOS ला सपोर्ट नाही" + "सॅटेलाइट SOS सेट केलेले नाही" + "तुम्ही इंटरनेटशी कनेक्ट केले असल्याची खात्री करा आणि पुन्हा सेटअप करून पहा" + "सॅटेलाइट SOS उपलब्ध नाही" + "या देशात किंवा प्रदेशात सॅटेलाइट SOS उपलब्ध नाही" + "सॅटेलाइट SOS सेट केलेले नाही" + "सॅटेलाइटद्वारे मेसेज करण्यासाठी, Google Messages ला तुमचे डीफॉल्ट मेसेजिंग ॲप म्हणून सेट करा" + "सॅटेलाइट SOS उपलब्ध नाही" + "या देशात किंवा प्रदेशात सॅटेलाइट SOS उपलब्ध आहे का हे तपासण्यासाठी, स्थान सेटिंग्ज सुरू करा" + "सॅटेलाइट मेसेजिंग उपलब्ध आहे" + "मोबाईल किंवा वाय-फाय नेटवर्क नसल्यास, तुम्ही सॅटेलाइटद्वारे मेसेज पाठवू शकता. Google Messages हे तुमचे डीफॉल्ट मेसेजिंग ॲप असणे आवश्यक आहे." + "सॅटेलाइट मेसेजिंगला सपोर्ट नाही" + "या डिव्हाइसवर सॅटेलाइट मेसेजिंगला सपोर्ट नाही" + "सॅटेलाइट मेसेजिंग सेट केलेले नाही" + "तुम्ही इंटरनेटशी कनेक्ट केले असल्याची खात्री करा आणि पुन्हा सेटअप करून पहा" + "सॅटेलाइट मेसेजिंग उपलब्ध नाही" + "या देशामध्ये किंवा प्रदेशामध्ये सॅटेलाइट मेसेजिंग उपलब्ध नाही" + "सॅटेलाइट मेसेजिंग सेट केलेले नाही" + "सॅटेलाइटद्वारे मेसेज करण्यासाठी, Google Messages ला तुमचे डीफॉल्ट मेसेजिंग ॲप म्हणून सेट करा" + "सॅटेलाइट मेसेजिंग उपलब्ध नाही" + "या देशात किंवा प्रदेशात सॅटेलाइट मेसेजिंग उपलब्ध आहे का हे तपासण्यासाठी, स्थान सेटिंग्ज सुरू करा" "फिंगरप्रिंट अनलॉक पुन्हा सेट करा" "%s यापुढे ओळखता येणार नाही." "%1$s आणि %2$s यापुढे ओळखता येणार नाहीत." diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 6f5d5e7c36589b4a2dcecb4565610ac149b3f259..bd780ae0be928e5772d0b260451d507c6cbd1b7e 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -612,6 +612,10 @@ "Benarkan apl menentukan kedudukan relatif antara peranti Ultrajalur Lebar berdekatan" "berinteraksi dengan peranti Wi-Fi berdekatan" "Membenarkan apl mengiklankan, menyambung dan menentukan kedudukan relatif peranti Wi-Fi berdekatan" + + + + "Maklumat Perkhidmatan Pembayaran NFC Pilihan" "Membenarkan apl mendapatkan maklumat perkhidmatan pembayaran nfc pilihan seperti bantuan berdaftar dan destinasi laluan." "mengawal Komunikasi Medan Dekat" @@ -2431,54 +2435,30 @@ "Hidupkan" "Kembali" "Belum selesai..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS via Satelit kini tersedia" + "Anda boleh menghantar mesej kepada perkhidmatan kecemasan jika tiada rangkaian mudah alih atau Wi-Fi. Google Messages mestilah ditetapkan sebagai apl pemesejan lalai anda." + "SOS via Satelit tidak disokong" + "SOS via Satelit tidak disokong pada peranti ini" + "SOS via Satelit tidak disediakan" + "Pastikan anda disambungkan kepada Internet dan cuba buat persediaan sekali lagi" + "SOS via Satelit tidak tersedia" + "SOS via Satelit tidak tersedia di negara atau rantau ini" + "SOS via Satelit tidak disediakan" + "Untuk menghantar mesej melalui satelit, tetapkan Google Messages sebagai apl pemesejan lalai anda" + "SOS via Satelit tidak tersedia" + "Untuk menyemak sama ada SOS via Satelit tersedia di negara atau rantau ini, hidupkan tetapan lokasi" + "Permesejan satelit tersedia" + "Anda boleh menghantar mesej melalui satelit jika tiada rangkaian mudah alih atau Wi-Fi. Google Messages mestilah ditetapkan sebagai apl pemesejan lalai anda." + "Permesejan satelit tidak disokong" + "Permesejan satelit tidak disokong pada peranti ini" + "Permesejan satelit tidak disediakan" + "Pastikan anda disambungkan kepada Internet dan cuba buat persediaan sekali lagi" + "Permesejan satelit tidak tersedia" + "Permesejan satelit tidak tersedia di negara atau rantau ini" + "Permesejan satelit tidak disediakan" + "Untuk menghantar mesej melalui satelit, tetapkan Google Messages sebagai apl pemesejan lalai anda" + "Permesejan satelit tidak tersedia" + "Untuk menyemak sama ada permesejan satelit tersedia di negara atau rantau ini, hidupkan tetapan lokasi" "Sediakan Buka Kunci Cap Jari sekali lagi" "%s tidak dapat dicam lagi." "%1$s dan %2$s tidak dapat dicam lagi." diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 9e7e78f98c6421b9875855505bffd8bf55f59353..e4c2acaba9f5f7b78384fce7fabba40e5ad41cd5 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -612,6 +612,10 @@ "အနီးရှိ ‘အလွန်ကျယ်ပြန့်သော လှိုင်းအလျားသုံးစက်များ’ ကြား မှန်းခြေနေရာကို သတ်မှတ်ရန် အက်ပ်ကို ခွင့်ပြုမည်" "အနီးရှိ Wi-Fi စက်များနှင့် ပြန်လှန်တုံ့ပြန်ခြင်း" "ကြော်ငြာရန်၊ ချိတ်ဆက်ရန်နှင့် အနီးတစ်ဝိုက်ရှိ Wi-Fi စက်များ၏ နေရာကို သတ်မှတ်ရန် အက်ပ်ကို ခွင့်ပြုသည်" + + + + "ဦးစားပေး NFC ငွေပေးချေမှုဆိုင်ရာ ဝန်ဆောင်မှု အချက်အလက်များ" "အက်ပ်အား ဦစားပေး NFC ငွေပေးချေမှုဆိုင်ရာ ဝန်ဆောင်မှု အချက်အလက်များဖြစ်သည့် မှတ်ပုံတင်ထားသော အကူအညီများနှင့် သွားလာရာ လမ်းကြောင်းတို့ကို ရယူရန် ခွင့်ပြုသည်။" "Near Field Communicationအား ထိန်းချုပ်ရန်" @@ -2431,54 +2435,30 @@ "ဖွင့်ရန်" "နောက်သို့" "ဆိုင်းငံ့ထားသည်…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Satellite SOS ကို ယခုသုံးနိုင်ပြီ" + "မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက် မရှိပါက အရေးပေါ်ဝန်ဆောင်မှု ဌာနများသို့ မက်ဆေ့ဂျ်ပို့နိုင်သည်။ Google Messages သည် သင်၏ မူရင်းမက်ဆေ့ဂျ်ပို့ရန်အက်ပ် ဖြစ်ရမည်။" + "Satellite SOS ကို ပံ့ပိုးမထားပါ" + "Satellite SOS ကို ဤစက်တွင် ပံ့ပိုးမထားပါ" + "Satellite SOS ကို စနစ်ထည့်သွင်းမထားပါ" + "အင်တာနက်နှင့် ချိတ်ဆက်ထားခြင်း ရှိ၊ မရှိ စစ်ဆေးပြီး စနစ်ထပ်မံထည့်သွင်းကြည့်ပါ" + "Satellite SOS မရနိုင်ပါ" + "Satellite SOS ကို ဤနိုင်ငံ (သို့) ဒေသတွင် မရနိုင်ပါ" + "Satellite SOS ကို စနစ်ထည့်သွင်းမထားပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ရန် Google Messages ကို သင်၏ မက်ဆေ့ဂျ်ပို့ရန် မူရင်းအက်ပ်အဖြစ် သတ်မှတ်ပါ" + "Satellite SOS မရနိုင်ပါ" + "satellite SOS ကို ဤနိုင်ငံ (သို့) ဒေသတွင် ရနိုင်ခြင်းရှိ၊ မရှိ စစ်ဆေးရန် တည်နေရာပြ ဆက်တင်များကို ဖွင့်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်း ရနိုင်သည်" + "မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက် မရှိသည့်အခါ ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့နိုင်သည်။ Google Messages သည် သင်၏ မူရင်းမက်ဆေ့ဂျ်ပို့ရန်အက်ပ် ဖြစ်ရမည်။" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို ပံ့ပိုးမထားပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို ဤစက်တွင် ပံ့ပိုးမထားပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို စနစ်ထည့်သွင်းမထားပါ" + "အင်တာနက်နှင့် ချိတ်ဆက်ထားခြင်း ရှိ၊ မရှိ စစ်ဆေးပြီး စနစ်ထပ်မံထည့်သွင်းကြည့်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို မရနိုင်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို ဤနိုင်ငံ (သို့) ဒေသတွင် မရနိုင်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို စနစ်ထည့်သွင်းမထားပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ရန် Google Messages ကို သင်၏ မက်ဆေ့ဂျ်ပို့ရန် မူရင်းအက်ပ်အဖြစ် သတ်မှတ်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို မရနိုင်ပါ" + "ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်းကို ဤနိုင်ငံ (သို့) ဒေသတွင် ရနိုင်ခြင်းရှိ၊ မရှိ စစ်ဆေးရန် တည်နေရာပြ ဆက်တင်များကို ဖွင့်ပါ" "‘လက်ဗွေသုံး လော့ခ်ဖွင့်ခြင်း’ ကို စနစ်ထပ်မံထည့်သွင်းပါ" "%s ကို မသိရှိနိုင်တော့ပါ။" "%1$s နှင့် %2$s ကို မသိရှိနိုင်တော့ပါ။" diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 683ca2661b6b42b4a1e88976fad737d71e87656d..ec4cd961faee2787cde8e28c7921ef5ee9922a08 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -612,6 +612,10 @@ "tillate at appen fastslår den relative posisjonen mellom enheter i nærheten som bruker ultrabredbånd" "samhandle med wifi-enheter i nærheten" "Lar appen annonsere, koble til og fastslå den relative posisjonen til wifi-enheter i nærheten" + + + + "Informasjon om prioritert NFC-betalingstjeneste" "Gir appen tilgang til informasjon om prioritert NFC-betalingstjeneste, for eksempel registrerte hjelpemidler og destinasjon." "kontroller overføring av data med NFC-teknologi" diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index ffeecb1cf0ba03f7949cd6b52ad1aacb1709c555..0dc0bd3d04a982756888d352a73b11877ba676c0 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -293,7 +293,7 @@ "खाताको स्थिति" "विकासकर्ताका म्यासेजहरू" "विकासकर्तासम्बन्धी महत्त्वपूर्ण म्यासेजहरू" - "अद्यावधिकहरू" + "अपडेटहरू" "नेटवर्कको स्थिति" "नेटवर्कका अलर्टहरू" "नेटवर्क उपलब्ध छ" @@ -612,6 +612,10 @@ "यो एपलाई नजिकै रहेका अल्ट्रा-वाइडब्यान्ड चल्ने डिभाइसहरूबिचको तुलनात्मक स्थान पत्ता लगाउन दिनुहोस्" "Wi-Fi चल्ने नजिकै रहेका डिभाइसहरूमा चलाउन दिन्छ" "यसले एपलाई Wi-Fi चल्ने नजिकै रहेका डिभाइसहरूमा विज्ञापन गर्न, कनेक्ट गर्न र सापेक्ष स्थिति निर्धारण गर्न दिन्छ" + + + + "NFC भुक्तानी सेवासम्बन्धी रुचाइएको जानकारी" "यसले एपलाई दर्ता गरिएका सहायता तथा मार्गको गन्तव्य जस्ता रुचाइएका NFC भुक्तानी सेवासम्बन्धी जानकारी प्राप्त गर्न दिन्छ।" "नजिक क्षेत्र संचार नियन्त्रणहरू" @@ -2431,54 +2435,30 @@ "अन गर्नुहोस्" "पछाडि जानुहोस्" "विचाराधीन..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "स्याटलाइट SOS अब उपलब्ध भएको छ" + "मोबाइल वा Wi-Fi नेटवर्क नभएका खण्डमा तपाईं आपत्‍कालीन सेवामा म्यासेज पठाउन सक्नुहुन्छ। Google Messages अनिवार्य रूपमा तपाईंको डिफल्ट म्यासेजिङ एप हुनु पर्छ।" + "स्याटलाइट SOS प्रयोग गर्न मिल्दैन" + "यो डिभाइसमा स्याटलाइट SOS प्रयोग गर्न मिल्दैन" + "स्याटलाइट SOS सेटअप गरिएको छैन" + "तपाईंको डिभाइस इन्टरनेटमा कनेक्ट गरिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस् र फेरि सेटअप गरी हेर्नुहोस्" + "स्याटलाइट SOS उपलब्ध छैन" + "यो देश वा क्षेत्रमा स्याटलाइट SOS उपलब्ध छैन" + "स्याटलाइट SOS सेटअप गरिएको छैन" + "स्याटलाइटमार्फत म्यासेज पठाउन Google Messages लाई डिफल्ट म्यासेजिङ एपका रूपमा सेट गर्नुहोस्" + "स्याटलाइट SOS उपलब्ध छैन" + "यो देश वा क्षेत्रमा स्याटलाइट SOS उपलब्ध छ कि छैन भन्ने कुरा जाँच्न लोकेसन सेटिङ अन गर्नुहोस्" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा उपलब्ध छ" + "तपाईं मोबाइल वा Wi-Fi नेटवर्क उपलब्ध नभएका खण्डमा स्याटलाइटमार्फत म्यासेज पठाउन सक्नुहुन्छ। Google Messages अनिवार्य रूपमा तपाईंको डिफल्ट म्यासेजिङ एप हुनु पर्छ।" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा प्रयोग गर्न मिल्दैन" + "यो डिभाइसमा स्याटलाइटमार्फत म्यासेज पठाउने सुविधा प्रयोग गर्न मिल्दैन" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा सेटअप गरिएको छैन" + "तपाईंको डिभाइस इन्टरनेटमा कनेक्ट गरिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस् र फेरि सेटअप गरी हेर्नुहोस्" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा उपलब्ध छैन" + "यो देश वा क्षेत्रमा स्याटलाइटमार्फत म्यासेज पठाउने सुविधा उपलब्ध छैन" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा सेटअप गरिएको छैन" + "स्याटलाइटमार्फत म्यासेज पठाउन Google Messages लाई डिफल्ट म्यासेजिङ एपका रूपमा सेट गर्नुहोस्" + "स्याटलाइटमार्फत म्यासेज पठाउने सुविधा उपलब्ध छैन" + "यो देश वा क्षेत्रमा स्याटलाइटमार्फत म्यासेज पठाउने सुविधा उपलब्ध छ कि छैन भन्ने कुरा जाँच्न लोकेसन सेटिङ अन गर्नुहोस्" "फिंगरप्रिन्ट अनलक फेरि सेटअप गर्नुहोस्" "%s अब पहिचान गर्न सकिँदैन।" "%1$s%2$s अब पहिचान गर्न सकिँदैन।" diff --git a/core/res/res/values-night/colors_dynamic.xml b/core/res/res/values-night/colors_dynamic.xml new file mode 100644 index 0000000000000000000000000000000000000000..7e95ff4f409e492c9260dd2d9b027a24ac6e9c18 --- /dev/null +++ b/core/res/res/values-night/colors_dynamic.xml @@ -0,0 +1,103 @@ + + + + + + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark + @color/system_error_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark + @color/system_on_background_dark + @color/system_on_error_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark + @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark + @color/system_outline_dark + @color/system_outline_variant_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark + @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark + @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark + @color/system_tertiary_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim + @color/system_brand_a_dark + @color/system_brand_b_dark + @color/system_brand_c_dark + @color/system_brand_d_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark + @color/system_on_shade_active_dark + @color/system_on_shade_active_variant_dark + @color/system_on_shade_inactive_dark + @color/system_on_shade_inactive_variant_dark + @color/system_on_theme_app_dark + @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark + diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 5f77e7304cdbfb7b41d2281a643d64e57ec3662e..e553347407786187e8549b6566e2c6c3624dab48 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -612,6 +612,10 @@ "De app toestaan om de relatieve positie tussen ultrabreedbandapparaten in de buurt te bepalen" "interactie met wifi-apparaten in de buurt" "Hiermee kan de app uitzenden, verbindingen maken en de relatieve positie bepalen van wifi-apparaten in de buurt" + + + + "Informatie over voorkeursservice voor NFC-betaling" "Hiermee kun je zorgen dat de app informatie krijgt over de voorkeursservice voor NFC-betaling, zoals geregistreerde hulpmiddelen en routebestemmingen." "Near Field Communication regelen" @@ -2431,54 +2435,30 @@ "Aanzetten" "Terug" "In behandeling…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS via satelliet is nu beschikbaar" + "Je kunt berichten sturen naar de hulpdiensten als er geen mobiel of wifi-netwerk beschikbaar is. Google Berichten moet je standaard berichten-app zijn." + "SOS via satelliet wordt niet ondersteund" + "SOS via satelliet wordt niet ondersteund op dit apparaat" + "SOS via satelliet is niet ingesteld" + "Zorg dat je verbinding hebt met internet en probeer het opnieuw" + "SOS via satelliet is niet beschikbaar" + "SOS via satelliet is niet beschikbaar in dit land of deze regio" + "SOS via satelliet niet ingesteld" + "Stel Google Berichten in als je standaard berichten-app om via satelliet berichten te sturen" + "SOS via satelliet is niet beschikbaar" + "Zet locatie-instellingen aan om te checken of SOS via satelliet beschikbaar is in dit land of deze regio" + "Satellietberichten beschikbaar" + "Je kunt via satelliet berichten sturen als er geen mobiel of wifi-netwerk beschikbaar is. Google Berichten moet je standaard berichten-app zijn." + "Satellietberichten niet ondersteund" + "Satellietberichten worden niet ondersteund op dit apparaat" + "Satellietberichten niet ingesteld" + "Zorg dat je verbinding hebt met internet en probeer het opnieuw" + "Satellietberichten niet beschikbaar" + "Satellietberichten zijn niet beschikbaar in dit land of deze regio" + "Satellietberichten niet ingesteld" + "Stel Google Berichten in als je standaard berichten-app om via satelliet berichten te sturen" + "Satellietberichten niet beschikbaar" + "Zet locatie-instellingen aan om te checken of satellietberichten beschikbaar zijn in dit land of deze regio" "Ontgrendelen met vingerafdruk weer instellen" "%s wordt niet meer herkend." "%1$s en %2$s worden niet meer herkend." diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index b4f924b7d9dbf2b07bf7db75bd4a17dd8bc6b303..2515bb5052eb798c460e0811bba1847f2151ea42 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -612,6 +612,10 @@ "ଆଖପାଖର ଅଲଟ୍ରା-ୱାଇଡବ୍ୟାଣ୍ଡ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପେକ୍ଷିକ ଅବସ୍ଥିତିକୁ ନିର୍ଦ୍ଧାରଣ କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଅନ୍ତୁ" "ଆଖପାଖର ୱାଇ-ଫାଇ ଡିଭାଇସଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରନ୍ତୁ" "ଆଖପାଖର ୱାଇ-ଫାଇ ଡିଭାଇସରେ ବିଜ୍ଞାପନ ଦେବା, ତା ସହ ସଂଯୋଗ କରିବା ଓ ତା’ର ଆପେକ୍ଷିକ ଅବସ୍ଥିତି ନିର୍ଦ୍ଧାରଣ କରିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ" + + + + "ପସନ୍ଦର NFC ପେମେଣ୍ଟ ସେବା ସୂଚନା" "ପଞ୍ଜିକୃତ ଯନ୍ତ୍ର ଏବଂ ମାର୍ଗ ଲକ୍ଷସ୍ଥଳ ପରି ପସନ୍ଦର nfc ପେମେଣ୍ଟ ସେବା ସୂଚନା ପାଇବାକୁ ଆପ୍ ଅନୁମତି କରିଥାଏ।" "ନିଅର୍ ଫିଲ୍ଡ କମ୍ୟୁନିକେଶନ୍ ଉପରେ ନିୟନ୍ତ୍ରଣ ରଖନ୍ତୁ" diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index fae806c4decab7d8afeedbaa6b86ac0903b584fc..574d991b9dfcaf34fb842fbb57d6e8ce21ad014c 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -612,6 +612,10 @@ "ਐਪ ਨੂੰ ਨਜ਼ਦੀਕੀ ਅਲਟ੍ਰਾ-ਵਾਈਡਬੈਂਡ ਡੀਵਾਈਸਾਂ ਦੇ ਵਿਚਾਲੇ ਸੰਬੰਧਿਤ ਸਥਿਤੀ ਨੂੰ ਨਿਰਧਾਰਿਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ" "ਨਜ਼ਦੀਕੀ ਵਾਈ-ਫਾਈ ਡੀਵਾਈਸਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰੋ" "ਐਪ ਨੂੰ ਨਜ਼ਦੀਕੀ ਵਾਈ-ਫਾਈ ਡੀਵਾਈਸਾਂ \'ਤੇ ਵਿਗਿਆਪਨ ਦੇਣ, ਕਨੈਕਟ ਕਰਨ ਅਤੇ ਉਨ੍ਹਾਂ ਦੀ ਸੰਬੰਧਿਤ ਸਥਿਤੀ ਨੂੰ ਨਿਰਧਾਰਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ" + + + + "ਤਰਜੀਹੀ NFC ਭੁਗਤਾਨਸ਼ੁਦਾ ਸੇਵਾ ਜਾਣਕਾਰੀ" "ਐਪ ਨੂੰ ਤਰਜੀਹੀ NFC ਭੁਗਤਾਨਸ਼ੁਦਾ ਸੇਵਾ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰਨ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਕਿ ਰਜਿਸਟਰ ਕੀਤੇ ਸਾਧਨ ਅਤੇ ਮੰਜ਼ਿਲ ਰਸਤਾ।" "ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ" @@ -2431,54 +2435,30 @@ "ਚਾਲੂ ਕਰੋ" "ਵਾਪਸ ਜਾਓ" "ਵਿਚਾਰ-ਅਧੀਨ..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਹੁਣ ਉਪਲਬਧ ਹੈ" + "ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਨਾ ਹੋਣ \'ਤੇ, ਤੁਸੀਂ ਐਮਰਜੈਂਸੀ ਸੇਵਾਵਾਂ ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਸਕਦੇ ਹੋ। Google Messages ਤੁਹਾਡੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੁਨੇਹਾ ਐਪ ਹੋਣੀ ਲਾਜ਼ਮੀ ਹੈ।" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਦਾ ਸੈੱਟ ਅੱਪ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਇੰਟਰਨੈੱਟ ਨਾਲ ਕਨੈਕਟ ਹੋ ਅਤੇ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਇਸ ਦੇਸ਼ ਜਾਂ ਖੇਤਰ ਵਿੱਚ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਦਾ ਸੈੱਟ ਅੱਪ ਨਹੀਂ ਹੋਇਆ" + "ਸੈਟੇਲਾਈਟ ਰਾਹੀਂ ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ, Google Messages ਨੂੰ ਆਪਣੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੁਨੇਹਾ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰੋ" + "ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਇਸਦੀ ਜਾਂਚ ਕਰਨ ਲਈ ਕਿ ਇਸ ਦੇਸ਼ ਜਾਂ ਖੇਤਰ ਵਿੱਚ ਸੈਟੇਲਾਈਟ SOS ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਹੈ, ਟਿਕਾਣਾ ਸੈਟਿੰਗਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਹੈ" + "ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਨਾ ਹੋਣ \'ਤੇ, ਤੁਸੀਂ ਸੈਟੇਲਾਈਟ ਰਾਹੀਂ ਸੁਨੇਹੇ ਭੇਜ ਸਕਦੇ ਹੋ। Google Messages ਤੁਹਾਡੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੁਨੇਹਾ ਐਪ ਹੋਣੀ ਲਾਜ਼ਮੀ ਹੈ।" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਦਾ ਸੈੱਟਅੱਪ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਇੰਟਰਨੈੱਟ ਨਾਲ ਕਨੈਕਟ ਹੋ ਅਤੇ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਇਸ ਦੇਸ਼ ਜਾਂ ਖੇਤਰ ਵਿੱਚ ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਦਾ ਸੈੱਟਅੱਪ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਸੈਟੇਲਾਈਟ ਰਾਹੀਂ ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ, Google Messages ਨੂੰ ਆਪਣੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੁਨੇਹਾ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰੋ" + "ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਨਹੀਂ ਹੈ" + "ਇਹ ਜਾਂਚ ਕਰਨ ਲਈ ਕਿ ਇਸ ਦੇਸ਼ ਜਾਂ ਖੇਤਰ ਵਿੱਚ ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਸੁਵਿਧਾ ਉਪਲਬਧ ਹੈ ਜਾਂ ਨਹੀਂ, ਟਿਕਾਣਾ ਸੈਟਿੰਗਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ" "ਫਿੰਗਰਪ੍ਰਿੰਟ ਅਣਲਾਕ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ" "%s ਦੀ ਹੁਣ ਪਛਾਣ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।" "%1$s ਅਤੇ %2$s ਦੀ ਹੁਣ ਪਛਾਣ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।" diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index cf758feb97ae182e9d4b3ca0695aeacdc20958b1..f267877b3a18f3e4e13952f620abe5b660cb795a 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -590,7 +590,7 @@ "Pozwala aplikacji na odbieranie pakietów wysyłanych przez sieć Wi-Fi do wszystkich urządzeń, a nie tylko do Twojego tabletu, przy użyciu adresów połączeń grupowych. Powoduje większe zapotrzebowanie na energię niż w trybie innym niż grupowy." "Pozwala aplikacji odbierać pakiety wysyłane przez sieć Wi-Fi do wszystkich urządzeń (a nie tylko do Twojego urządzenia z Androidem TV) przy użyciu adresów połączeń grupowych. Powoduje większe zapotrzebowanie na energię niż w innych trybach." "Pozwala aplikacji na odbieranie pakietów wysyłanych przez sieć Wi-Fi do wszystkich urządzeń, a nie tylko do Twojego telefonu, przy użyciu adresów połączeń grupowych. Powoduje większe zapotrzebowanie na energię niż w trybie innym niż grupowy." - "uzyskiwanie dostępu do ustawień Bluetooth" + "uzyskiwanie dostępu do ustawień Bluetootha" "Pozwala aplikacji na konfigurowanie lokalnego tabletu z funkcją Bluetooth oraz na wykrywanie urządzeń zdalnych i parowanie z nimi." "Pozwala aplikacji konfigurować Bluetootha na urządzeniu z Androidem TV oraz wykrywać urządzenia zdalne i przeprowadzać parowanie z nimi." "Pozwala aplikacji na konfigurowanie lokalnego telefonu z funkcją Bluetooth oraz na wykrywanie urządzeń zdalnych i parowanie z nimi." @@ -614,6 +614,10 @@ "Zezwól na określanie przez aplikację względnego położenia urządzeń ultraszerokopasmowych w pobliżu" "interakcje z urządzeniami Wi-Fi w pobliżu" "Zezwala aplikacji na przesyłanie informacji o sobie, łączenie się z urządzeniami Wi‑Fi w pobliżu i określanie ich względnego położenia" + + + + "Informacje o preferowanych usługach płatniczych NFC" "Pozwala aplikacji uzyskiwać informacje o preferowanych usługach płatniczych NFC, np. zarejestrowanych pomocach i miejscach docelowych tras." "kontrolowanie łączności Near Field Communication" @@ -2433,54 +2437,30 @@ "Włącz" "Wróć" "Oczekiwanie…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Satelitarne połączenie alarmowe jest już dostępne" + "Możesz wysłać wiadomość do służb ratunkowych, jeśli masz problem z połączeniem się z siecią komórkową lub Wi-Fi. Wiadomości Google muszą być domyślną aplikacją do SMS-ów." + "Satelitarne połączenie alarmowe nie jest obsługiwane" + "To urządzenie nie obsługuje satelitarnego połączenia alarmowego" + "Satelitarne połączenie alarmowe nie jest skonfigurowane" + "Upewnij się, że masz połączenie z internetem, i spróbuj ponownie" + "Satelitarne połączenie alarmowe nie jest dostępne" + "Satelitarne połączenie alarmowe nie jest dostępne w tym kraju lub regionie" + "Satelitarne połączenie alarmowe nie zostało skonfigurowane" + "Aby wysyłać wiadomości przez satelitę, ustaw Wiadomości Google jako domyślną aplikację do obsługi SMS-ów" + "Satelitarne połączenie alarmowe nie jest dostępne" + "Aby sprawdzić, czy satelitarne połączenie alarmowe jest dostępne w tym kraju lub regionie, włącz ustawienia lokalizacji" + "Przesyłanie wiadomości przez satelitę jest dostępne" + "Możesz wysyłać wiadomości przez satelitę, jeśli nie masz dostępu do sieci komórkowej lub Wi-Fi. Wiadomości Google muszą być domyślną aplikacją do SMS-ów." + "Przesyłanie wiadomości przez satelitę nie jest obsługiwane" + "To urządzenie nie obsługuje przesyłania wiadomości przez satelitę" + "Nie skonfigurowano przesyłania wiadomości przez satelitę" + "Upewnij się, że masz połączenie z internetem, i spróbuj ponownie" + "Przesyłanie wiadomości przez satelitę jest niedostępne" + "Przesyłanie wiadomości przez satelitę nie jest dostępne w tym kraju lub regionie" + "Nie skonfigurowano przesyłania wiadomości przez satelitę" + "Aby wysyłać wiadomości przez satelitę, ustaw Wiadomości Google jako domyślną aplikację do obsługi SMS-ów" + "Przesyłanie wiadomości przez satelitę jest niedostępne" + "Aby sprawdzić, czy przesyłanie wiadomości przez satelitę jest dostępne w tym kraju lub regionie, włącz ustawienia lokalizacji" "Skonfiguruj ponownie odblokowywanie odciskiem palca" "Ten odcisk palca (%s) nie jest już rozpoznawany." "Te odciski palców (%1$s%2$s) nie są już rozpoznawane." diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 71b334e89eafc796d4b2aed3d69fd3221e5399e4..1719648d10f04e31ae41cae3387ffdbe7b3ffca3 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -613,6 +613,10 @@ "Permitir que o app determine o posicionamento relativo entre dispositivos de banda ultralarga por perto" "interagir com dispositivos Wi-Fi por perto" "Permite que o app divulgue, faça conexão e determine a posição relativa de dispositivos Wi-Fi por perto." + + + + "Informações preferidas de serviço de pagamento por NFC" "Permite que o app acesse as informações preferidas de serviço de pagamento por NFC, como auxílios registrados ou destinos de trajetos." "controlar a comunicação a curta distância" @@ -1755,7 +1759,7 @@ "Usar atalho" "Inversão de cores" "Correção de cor" - "Modo uma mão" + "Modo para uma mão" "Escurecer ainda mais a tela" "Aparelhos auditivos" "Teclas de volume pressionadas. Serviço %1$s ativado." @@ -2432,54 +2436,30 @@ "Ativar" "Voltar" "Pendente…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "O SOS via satélite já está disponível" + "Você poderá enviar uma mensagem para serviços de emergência se não houver uma rede móvel ou Wi-Fi. O Google Mensagens precisa ser seu app de mensagens padrão." + "Incompatível com o SOS via satélite" + "Este dispositivo não é compatível com o SOS via satélite" + "O SOS via satélite não foi configurado" + "Verifique sua conexão de Internet e tente configurar de novo" + "SOS via satélite indisponível" + "O SOS via satélite não está disponível neste país ou região" + "SOS via satélite não configurado" + "Para enviar mensagens via satélite, defina o Google Mensagens como seu app de mensagens padrão" + "SOS via satélite indisponível" + "Para verificar se o SOS via satélite está disponível neste país ou região, ative as configurações de localização" + "Mensagem via satélite disponível" + "É possível trocar mensagens por satélite caso não haja uma rede móvel ou Wi-Fi. O Google Mensagens precisa ser seu app de mensagens padrão." + "Incompatível com mensagem via satélite" + "Este dispositivo não é compatível com mensagem via satélite" + "Mensagem via satélite não configurada" + "Verifique sua conexão de Internet e tente configurar de novo" + "Mensagem via satélite indisponível" + "A mensagem via satélite não está disponível neste país ou região" + "Mensagem via satélite não configurada" + "Para enviar mensagens via satélite, defina o Google Mensagens como seu app de mensagens padrão" + "Mensagem via satélite indisponível" + "Para verificar se a mensagem via satélite está disponível neste país ou região, ative as configurações de localização" "Configurar o Desbloqueio por impressão digital de novo" "A impressão digital %s não é mais reconhecida." "As impressões digitais %1$s e %2$s não são mais reconhecidas." diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 4d5ff474c84f90be2ae240655cf0314c19381593..b8fc6a96f7834e787e800061829d7a6140ad765f 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -613,6 +613,10 @@ "Permita que a app determine a posição relativa entre os dispositivos de banda ultralarga próximos" "interagir com dispositivos Wi‑Fi próximos" "Permite que a app anuncie, estabeleça ligação e determine a posição relativa de dispositivos Wi‑Fi próximos" + + + + "Informações de serviços de pagamento com NFC preferenciais" "Permite que a app obtenha informações de serviços de pagamento com NFC preferenciais, como apoios registados e destino da rota." "controlo Near Field Communication" @@ -2432,54 +2436,30 @@ "Ativar" "Retroceder" "Pendente…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "O Satélite SOS já está disponível" + "Pode enviar mensagens aos serviços de emergência se não tiver uma rede móvel nem Wi-Fi. A app Mensagens Google tem de ser a sua app de mensagens predefinida." + "O Satélite SOS não é compatível" + "O Satélite SOS não é compatível com este dispositivo" + "O Satélite SOS não está configurado" + "Certifique-se de que tem ligação à Internet e tente configurar novamente" + "O Satélite SOS não está disponível" + "O Satélite SOS não está disponível neste país ou região" + "Satélite SOS não configurado" + "Para enviar mensagens por satélite, defina a app Mensagens Google como a sua app de mensagens predefinida" + "O Satélite SOS não está disponível" + "Para verificar se o Satélite SOS está disponível neste país ou região, ative as definições de localização" + "Mensagens por satélite disponíveis" + "Pode enviar mensagens por satélite se não tiver uma rede móvel nem Wi-Fi. A app Mensagens Google tem de ser a sua app de mensagens predefinida." + "As mensagens por satélite não são compatíveis" + "As mensagens por satélite não são compatíveis com este dispositivo" + "Mensagens por satélite não configuradas" + "Certifique-se de que tem ligação à Internet e tente configurar novamente" + "Mensagens por satélite não disponíveis" + "As mensagens por satélite não estão disponíveis neste país ou região" + "Mensagens por satélite não configuradas" + "Para enviar mensagens por satélite, defina a app Mensagens Google como a sua app de mensagens predefinida" + "Mensagens por satélite não disponíveis" + "Para verificar se as mensagens por satélite estão disponíveis neste país ou região, ative as definições de localização" "Configure o Desbloqueio por impressão digital novamente" "Já não é possível reconhecer %s." "Já não é possível reconhecer %1$s e %2$s." diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 71b334e89eafc796d4b2aed3d69fd3221e5399e4..1719648d10f04e31ae41cae3387ffdbe7b3ffca3 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -613,6 +613,10 @@ "Permitir que o app determine o posicionamento relativo entre dispositivos de banda ultralarga por perto" "interagir com dispositivos Wi-Fi por perto" "Permite que o app divulgue, faça conexão e determine a posição relativa de dispositivos Wi-Fi por perto." + + + + "Informações preferidas de serviço de pagamento por NFC" "Permite que o app acesse as informações preferidas de serviço de pagamento por NFC, como auxílios registrados ou destinos de trajetos." "controlar a comunicação a curta distância" @@ -1755,7 +1759,7 @@ "Usar atalho" "Inversão de cores" "Correção de cor" - "Modo uma mão" + "Modo para uma mão" "Escurecer ainda mais a tela" "Aparelhos auditivos" "Teclas de volume pressionadas. Serviço %1$s ativado." @@ -2432,54 +2436,30 @@ "Ativar" "Voltar" "Pendente…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "O SOS via satélite já está disponível" + "Você poderá enviar uma mensagem para serviços de emergência se não houver uma rede móvel ou Wi-Fi. O Google Mensagens precisa ser seu app de mensagens padrão." + "Incompatível com o SOS via satélite" + "Este dispositivo não é compatível com o SOS via satélite" + "O SOS via satélite não foi configurado" + "Verifique sua conexão de Internet e tente configurar de novo" + "SOS via satélite indisponível" + "O SOS via satélite não está disponível neste país ou região" + "SOS via satélite não configurado" + "Para enviar mensagens via satélite, defina o Google Mensagens como seu app de mensagens padrão" + "SOS via satélite indisponível" + "Para verificar se o SOS via satélite está disponível neste país ou região, ative as configurações de localização" + "Mensagem via satélite disponível" + "É possível trocar mensagens por satélite caso não haja uma rede móvel ou Wi-Fi. O Google Mensagens precisa ser seu app de mensagens padrão." + "Incompatível com mensagem via satélite" + "Este dispositivo não é compatível com mensagem via satélite" + "Mensagem via satélite não configurada" + "Verifique sua conexão de Internet e tente configurar de novo" + "Mensagem via satélite indisponível" + "A mensagem via satélite não está disponível neste país ou região" + "Mensagem via satélite não configurada" + "Para enviar mensagens via satélite, defina o Google Mensagens como seu app de mensagens padrão" + "Mensagem via satélite indisponível" + "Para verificar se a mensagem via satélite está disponível neste país ou região, ative as configurações de localização" "Configurar o Desbloqueio por impressão digital de novo" "A impressão digital %s não é mais reconhecida." "As impressões digitais %1$s e %2$s não são mais reconhecidas." diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 6e43d317e0f05f878be4a53379e637775153cfed..7843bbe1e9f1b945ef3cf2b8c4111cf2d0947227 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -613,6 +613,10 @@ "Permite-i aplicației să stabilească poziția relativă dintre dispozitivele Ultra-Wideband din apropiere" "să interacționeze cu dispozitive Wi‑Fi din apropiere" "Permite aplicației să se conecteze la dispozitive Wi-Fi din apropiere, să transmită anunțuri și să stabilească poziția relativă a acestora" + + + + "Informații despre serviciul de plăți NFC preferat" "Permite aplicației să obțină informații despre serviciul de plăți NFC preferat, de exemplu, identificatorii de aplicație înregistrați și destinația traseului." "controlare schimb de date prin Near Field Communication" @@ -2432,54 +2436,30 @@ "Activează" "Înapoi" "În așteptare..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Funcția SOS prin satelit este acum disponibilă" + "Poți să trimiți mesaje serviciilor de urgență dacă nu este disponibilă o rețea mobilă sau Wi-Fi. Mesaje Google trebuie să fie aplicația ta pentru mesaje prestabilită." + "Funcția SOS prin satelit nu este acceptată" + "Funcția SOS prin satelit nu este acceptată pe acest dispozitiv" + "Funcția SOS prin satelit nu este configurată" + "Asigură-te că te-ai conectat la internet și încearcă din nou configurarea" + "Funcția SOS prin satelit nu este disponibilă" + "Funcția SOS prin satelit nu este disponibilă în această țară sau regiune" + "Funcția SOS prin satelit nu este configurată" + "Pentru a trimite mesaje prin satelit, setează Mesaje Google ca aplicație pentru mesaje prestabilită" + "Funcția SOS prin satelit nu este disponibilă" + "Pentru a verifica dacă funcția SOS prin satelit este disponibilă în această țară sau regiune, activează setările privind locația" + "Mesajele prin satelit sunt disponibile" + "Dacă nu este disponibilă o rețea mobilă sau Wi-Fi, poți să trimiți mesaje prin satelit. Mesaje Google trebuie să fie aplicația ta pentru mesaje prestabilită." + "Mesajele prin satelit nu sunt acceptate" + "Mesajele prin satelit nu sunt acceptate pe acest dispozitiv" + "Mesajele prin satelit nu sunt configurate" + "Asigură-te că te-ai conectat la internet și încearcă din nou configurarea" + "Mesajele prin satelit nu sunt disponibile" + "Mesajele prin satelit nu sunt disponibile în această țară sau regiune" + "Mesajele prin satelit nu sunt configurate" + "Pentru a trimite mesaje prin satelit, setează Mesaje Google ca aplicație pentru mesaje prestabilită" + "Mesajele prin satelit nu sunt disponibile" + "Pentru a verifica dacă mesajele prin satelit sunt disponibile în această țară sau regiune, activează setările privind locația" "Configurează din nou Deblocarea cu amprenta" "%s nu mai poate fi recunoscută." "%1$s și %2$s nu mai pot fi recunoscute." diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index b23a44bcc9fc40d99be8afb8860f08cf65958eaa..8caa8b60c120ce021ec421f96d641d8777e81d71 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -614,6 +614,10 @@ "Приложение сможет определять относительное позиционирование устройств с технологией сверхширокополосной связи поблизости" "Взаимодействие с устройствами Wi‑Fi поблизости" "Приложение сможет передавать данные на устройства Wi‑Fi рядом, подключаться к ним и определять их примерное местоположение." + + + + "Сведения о предпочтительном платежном сервисе NFC" "Приложение сможет получать сведения о предпочтительном платежном сервисе NFC (например, зарегистрированные идентификаторы AID и конечный пункт маршрута)." "Управление NFC-модулем" @@ -2433,54 +2437,30 @@ "Включить" "Назад" "Обработка…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Доступен спутниковый SOS" + "Вы можете отправлять сообщения экстренным службам, даже когда подключение по мобильной сети или Wi-Fi недоступно. Google Сообщения должны быть выбраны в качестве мессенджера по умолчанию." + "Спутниковый SOS не поддерживается" + "Функция недоступна на этом устройстве." + "Спутниковый SOS не настроен" + "Проверьте подключение к интернету и повторите попытку." + "Спутниковый SOS недоступен" + "Функция недоступна в этой стране или регионе." + "Спутниковый SOS не настроен" + "Чтобы использовать эту функцию, необходимо выбрать Google Сообщения в качестве мессенджера по умолчанию." + "Спутниковый SOS недоступен" + "Чтобы узнать, можно ли использовать спутниковый SOS в этой стране или регионе, включите настройки геолокации." + "Доступен спутниковый обмен сообщениями" + "Вы можете обмениваться сообщениями по спутниковой связи, даже когда подключение к мобильной сети или Wi-Fi недоступно. Google Сообщения должны быть выбраны в качестве мессенджера по умолчанию." + "Спутниковый обмен сообщениями не поддерживается" + "Функция недоступна на этом устройстве." + "Спутниковый обмен сообщениями не настроен" + "Проверьте подключение к интернету и повторите попытку." + "Спутниковый обмен сообщениями недоступен" + "Функция недоступна в этой стране или регионе." + "Спутниковый обмен сообщениями не настроен" + "Чтобы использовать эту функцию, необходимо выбрать Google Сообщения в качестве мессенджера по умолчанию." + "Спутниковый обмен сообщениями недоступен" + "Чтобы узнать, можно ли обмениваться сообщениями по спутниковой связи в этой стране или регионе, включите настройки геолокации." "Настройте разблокировку по отпечатку пальца заново" "Отпечаток \"%s\" больше нельзя распознать." "Отпечатки \"%1$s\" и \"%2$s\" больше нельзя распознать." diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index db82c6c98fa4f2a4db00c5a976bbe235169d562e..f150cd189f45e1849d68247b8edeac20d1b4fb09 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -612,6 +612,10 @@ "අවට ඇති අල්ට්‍රා-වයිඩ්බෑන්ඩ් උපාංග අතර සාපේක්ෂ පිහිටීම නිර්ණය කිරීමට යෙදුමට ඉඩ දීම" "අවට Wi‑Fi උපාංග සමග අන්තර්ක්‍රියා කරන්න" "වෙළඳ දැන්වීම් පළ කිරීමට, සම්බන්ධ වීමට සහ අවට ඇති Wi-Fi උපාංගවල සාපේක්ෂ පිහිටීම නිර්ණය කිරීමට යෙදුමට ඉඩ දෙයි" + + + + "කැමති NFC ගෙවීම් සේවා තොරතුරු" "ලියාපදිංචි කළ ආධාර සහ ගමන් මාර්ග ගමනාන්ත වැනි කැමති nfc ගෙවීම් සේවා තොරතුරු ලබා ගැනීමට යෙදුමට ඉඩ දෙයි." "ආසන්න ක්ෂේත්‍ර සන්නිවේදනය පාලනය කරන්න" diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index cbabfb6e107634d3f19711f4194cd15d10cb5abe..e17e25f4734dff244ae80e9e4e059d6c3f9bce1d 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -614,6 +614,10 @@ "Povoľte aplikácii určovať relatívnu polohu medzi zariadeniami s ultraširokopásmovým pripojením v okolí" "interakcia so zariadeniami Wi-Fi v okolí" "Umožňuje aplikácii oznamovať a rozpoznávať relatívnu polohu zariadení Wi‑Fi v okolí a pripájať sa k nim" + + + + "Preferované informácie platenej služby NFC" "Umožňuje aplikácii získavať preferované informácie platenej služby NFC, napríklad o registrovanej pomoci a trasách k cieľu." "ovládať technológiu NFC" diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index d7178c802ec6bb8577f6f90833b6863447a0ff73..da9f1c189610eb4318c60be52529a44f133a753a 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -614,6 +614,10 @@ "Aplikaciji dovoli, da določi relativno oddaljenost med napravami UWB v bližini." "komunikacija z napravami Wi‑Fi v bližini" "Aplikaciji dovoljuje objavljanje in določanje relativnega položaja naprav Wi‑Fi v bližini ter povezovanje z njimi." + + + + "Podatki o prednostni storitvi za plačevanje prek povezave NFC" "Aplikaciji omogoča pridobivanje podatkov o prednostni storitvi za plačevanje prek povezave NFC, kot so registrirani pripomočki in cilj preusmeritve." "nadzor nad komunikacijo s tehnologijo bližnjega polja" @@ -2433,54 +2437,30 @@ "Vklopi" "Nazaj" "V teku …" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS prek satelita je zdaj na voljo" + "Reševalnim službam lahko pošljete sporočilo, če ni povezave z mobilnim omrežjem ali omrežjem Wi-Fi. Google Sporočila morajo biti privzeta aplikacija za sporočanje." + "SOS prek satelita ni podprt" + "SOS prek satelita ni podprt v tej napravi" + "SOS prek satelita ni nastavljen" + "Zagotovite, da imate vzpostavljeno internetno povezavo, in znova poskusite nastaviti" + "SOS prek satelita ni na voljo" + "SOS prek satelita ni na voljo v tej državi ali regiji" + "SOS prek satelita ni nastavljen" + "Če želite pošiljati satelitska sporočila, nastavite Google Sporočila kot privzeto aplikacijo za sporočanje" + "SOS prek satelita ni na voljo" + "Če želite preveriti, ali je SOS prek satelita na voljo v tej državi ali regiji, vklopite nastavitve lokacije" + "Satelitska sporočila so na voljo" + "Satelitska sporočila lahko pošljete, če ni povezave z mobilnim omrežjem ali omrežjem Wi-Fi. Google Sporočila morajo biti privzeta aplikacija za sporočanje." + "Satelitska sporočila niso podprta" + "Satelitska sporočila niso podprta v tej napravi" + "Satelitska sporočila niso nastavljena" + "Zagotovite, da imate vzpostavljeno internetno povezavo, in znova poskusite nastaviti" + "Satelitska sporočila niso na voljo" + "Satelitska sporočila niso na voljo v tej državi ali regiji" + "Satelitska sporočila niso nastavljena" + "Če želite pošiljati satelitska sporočila, nastavite Google Sporočila kot privzeto aplikacijo za sporočanje" + "Satelitska sporočila niso na voljo" + "Če želite preveriti, ali so satelitska sporočila na voljo v tej državi ali regiji, vklopite nastavitve lokacije" "Vnovična nastavitev odklepanja s prstnim odtisom" "Odtisa »%s« ni več mogoče prepoznati." "Odtisov »%1$s« in »%2$s« ni več mogoče prepoznati." diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 42356bf8cc076661fc6bd004a892a7cd4f71ce13..1b8d0a0095f3c5d8cf8a4a9b737c12878493baaa 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -612,6 +612,10 @@ "Lejo që aplikacioni të përcaktojë pozicionin e përafërt mes pajisjeve në afërsi me brezin ultra të gjerë" "të ndërveprojë me pajisjet Wi-Fi në afërsi" "Lejon që aplikacioni të reklamojë, të lidhet dhe të përcaktojë pozicionin përkatës të pajisjeve Wi-Fi në afërsi" + + + + "Informacionet për shërbimin e preferuar të pagesës me NFC" "Lejon aplikacionin të marrë informacione për shërbimin e preferuar të pagesës me NFC si p.sh. ndihmat e regjistruara dhe destinacionin e itinerarit." "kontrollo \"Komunikimin e fushës në afërsi\" NFC" @@ -1663,7 +1667,7 @@ "Kufjet" "USB" "Sistemi" - "Audioja e \"bluetooth-it\"" + "Audioja e Bluetooth-it" "Ekran wireless" "Transmeto" "Lidhu me pajisjen" diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 10fa5f831fb4932584a90d906a2cd11adbf5357b..bdf557173391661ca1bfc3cc57e0eb24d0494489 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -613,6 +613,10 @@ "Дозвољава апликацији да одређује релативну раздаљину између уређаја ултра-широког појаса у близини" "интеракција са WiFi уређајима у близини" "Дозвољава апликацији да се оглашава, повезује и утврђује релативну позицију WiFi уређаја у близини" + + + + "Информације о жељеној NFC услузи за плаћање" "Дозвољава апликацији да преузима информације о жељеној NFC услузи за плаћање, попут регистрованих идентификатора апликација и одредишта преусмеравања." "контрола комуникације у ужем пољу (Near Field Communication)" @@ -2432,54 +2436,30 @@ "Укључи" "Назад" "На чекању..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Хитна помоћ преко сателита је сада доступна" + "Можете да шаљете поруке хитним службама ако немате приступ мобилној ни WiFi мрежи. Google Messages мора да буде подразумевана апликација за размену порука." + "Хитна помоћ преко сателита није подржана" + "Хитна помоћ преко сателита није подржанa на овом уређају" + "Хитна помоћ преко сателита није подешена" + "Проверите да ли сте повезани на интернет и пробајте поново да подесите" + "Хитна помоћ преко сателита није доступна" + "Хитна помоћ преко сателита није доступна у овој земљи или региону" + "Хитна помоћ преко сателита није подешена" + "Да бисте слали поруке преко сателита, подесите Google Messages као подразумевану апликацију за размену порука" + "Хитна помоћ преко сателита није доступна" + "Да бисте проверили да ли је хитна помоћ преко сателита доступна у овој земљи или региону, укључите подешавања локације" + "Размена порука преко сателита је доступна" + "Можете да шаљете поруке преко сателита ако немате приступ мобилној ни WiFi мрежи. Google Messages мора да буде подразумевана апликација за размену порука." + "Размена порука преко сателита није подржана" + "Размена порука преко сателита није подржана на овом уређају" + "Размена порука преко сателита није подешена" + "Проверите да ли сте повезани на интернет и пробајте поново да подесите" + "Размена порука преко сателита није доступна" + "Размена порука преко сателита није доступна у овој земљи или региону" + "Размена порука преко сателита није подешена" + "Да бисте слали поруке преко сателита, подесите Google Messages као подразумевану апликацију за размену порука" + "Размена порука преко сателита није доступна" + "Да бисте проверили да ли је размена порука преко сателита доступна у овој земљи или региону, укључите подешавања локације" "Поново подесите откључавање отиском прста" "%s више не може да се препозна." "%1$s и %2$s више не могу да се препознају." diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 857b060452ec56bc2bbcc82e6a37f290fce69ec2..8bef5ca8a8429ecd7392d61195a76525ad22d135 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -612,6 +612,10 @@ "Tillåt att appen fastställer den relativa positionen mellan Ultra Wideband-enheter i närheten" "interagera med wifi-enheter i närheten" "Tillåter appen att sända ut till, ansluta till och fastställa relativ position för wifi-enheter i närheten" + + + + "Information kopplad till standardtjänsten för NFC-betalning" "Tillåter att appen hämtar information kopplad till standardtjänsten för NFC-betalning, till exempel registrerade hjälpmedel och ruttdestinationer." "kontrollera närfältskommunikationen" @@ -2431,54 +2435,30 @@ "Aktivera" "Tillbaka" "Väntar …" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS-larm via satellit är nu tillgängligt" + "Du kan skicka meddelanden till räddningstjänsten om det inte finns någon mobil- eller wifi-anslutning. Google Messages måste vara din standardapp för meddelanden." + "SOS-larm via satellit stöds inte" + "SOS-larm via satellit stöds inte på den här enheten" + "SOS-larm via satellit har inte konfigurerats" + "Kontrollera att enheten är ansluten till internet och försök igen" + "SOS-larm via satellit är inte tillgängligt" + "SOS-larm via satellit är inte tillgängligt i det här landet eller den här regionen" + "SOS-larm via satellit har inte konfigurerats" + "Ställ in Google Messages som standardapp för meddelanden om du vill skicka meddelanden via satellit" + "SOS-larm via satellit är inte tillgängligt" + "Aktivera platsinställningar för att kontrollera om SOS-larm via satellit är tillgängligt i det här landet eller den här regionen" + "Satellitmeddelanden är tillgängliga" + "Du kan skicka meddelanden via satellit om det inte finns någon mobil- eller wifi-anslutning. Google Messages måste vara din standardapp för meddelanden." + "Satellitmeddelanden stöds inte" + "Satellitmeddelanden stöds inte på den här enheten" + "Satellitmeddelanden har inte ställts in" + "Kontrollera att enheten är ansluten till internet och försök igen" + "Satellitmeddelanden är inte tillgängliga" + "Satellitmeddelanden är inte tillgängliga i det här landet eller den här regionen" + "Satellitmeddelanden har inte ställts in" + "Ställ in Google Messages som standardapp för meddelanden om du vill skicka meddelanden via satellit" + "Satellitmeddelanden är inte tillgängliga" + "Aktivera platsinställningar för att kontrollera om satellitmeddelanden är tillgängliga i det här landet eller den här regionen" "Konfigurera fingeravtryckslås igen" "Det går inte längre att känna igen %s." "Det går inte längre att känna igen %1$s och %2$s." diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index d46fa084258bb9cab70854d8c0833f1e74da158a..010415eaf313168891313ed7d27f0edfaf59cba9 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -612,6 +612,10 @@ "Ruhusu programu ibainishe nafasi kati ya vifaa vyenye Bendi Pana Zaidi vilivyo karibu" "tumia vifaa vya Wi‑Fi vilivyo karibu" "Huruhusu programu kutangaza, kuunganisha na kubaini mahali palipokadiriwa vilipo vifaa vya Wi-Fi vilivyo karibu" + + + + "Maelezo ya Huduma Inayopendelewa ya Malipo ya NFC" "Huruhusu programu kupata maelezo ya huduma inayopendelewa ya malipo ya nfc kama vile huduma zilizosajiliwa na njia." "kudhibiti Mawasiliano ya Vifaa Vilivyokaribu (NFC)" @@ -2405,7 +2409,7 @@ "Muundo wa kibodi umewekwa kuwa %1$s, %2$s, %3$s… Gusa ili ubadilishe." "Mipangilio ya kibodi halisi imewekwa" "Gusa ili uangalie kibodi" - "Faragha" + "Sehemu ya Faragha" "Nakala" "Kazini" "Wa 2 wa Kazini" diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 32fefacb111596ef88340950dfd2eba733a40dca..1915f298261193836a084e0d31780139d3c27dab 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -612,6 +612,10 @@ "அருகிலுள்ள அல்ட்ரா-வைடுபேண்ட் சாதனங்களுக்கிடையிலான தூரத்தைத் தீர்மானிக்க ஆப்ஸை அனுமதிக்கும்" "அருகிலுள்ள வைஃபை சாதனங்களுடன் தொடர்பு கொள்ளுதல்" "அருகிலுள்ள வைஃபை சாதனங்களைத் தெரியப்படுத்தவும் இணைக்கவும் இருப்பிடத்தைத் தீர்மானிக்கவும் இது ஆப்ஸை அனுமதிக்கும்" + + + + "விருப்பமான NFC பேமெண்ட் சேவை தொடர்பான தகவல்கள்" "பதிவுசெய்யப்பட்ட கருவிகள், சேருமிடத்திற்கான வழி போன்ற விருப்பமான NFC பேமெண்ட் சேவை தொடர்பான தகவல்களைப் பெற ஆப்ஸை அனுமதிக்கிறது." "குறுகிய இடைவெளி தகவல்பரிமாற்றத்தைக் கட்டுப்படுத்துதல்" diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 8500e9dcd0d78fc40eb98cb61922bb0fd139c1e0..6ac81642d41f9916c718605fd968339671f7d927 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -612,6 +612,10 @@ "సమీపంలోని అల్ట్రా-వైడ్‌బ్యాండ్ పరికరాల మధ్య సాపేక్ష స్థానాన్ని నిర్ణయించడానికి యాప్‌ను అనుమతించండి" "సమీపంలోని Wi-Fi పరికరాలతో ఇంటరాక్ట్ చేస్తుంది" "అడ్వర్టయిజ్, కనెక్ట్ చేయడానికి, సమీపంలోని Wi-Fi పరికరాల సంబంధిత పొజిషన్‌ను నిర్ణయించడానికి యాప్‌ను అనుమతిస్తుంది" + + + + "ప్రాధాన్యత ఇవ్వబడిన NFC చెల్లింపు సేవల సమాచారం" "ప్రాధాన్యత ఇవ్వబడిన NFC చెల్లింపు సేవల సమాచారాన్ని, అంటే రిజిస్టర్ చేయబడిన సహాయక సాధనాలు, మార్గం, గమ్యస్థానం వంటి వాటిని పొందేందుకు యాప్‌ను అనుమతిస్తుంది." "సమీప క్షేత్ర కమ్యూనికేషన్‌ను నియంత్రించడం" diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index a1c7b4f59608af04b4698700560474912ab3795c..763eb626619891656720d2c9894062dae60b837f 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -612,6 +612,10 @@ "อนุญาตให้แอประบุตำแหน่งสัมพันธ์ระหว่างอุปกรณ์ที่ใช้แถบความถี่กว้างยิ่งยวดซึ่งอยู่ใกล้เคียง" "โต้ตอบกับอุปกรณ์ Wi-Fi ที่อยู่ใกล้เคียง" "อนุญาตให้แอปแสดงข้อมูล เชื่อมต่อ และระบุตำแหน่งซึ่งสัมพันธ์กับอุปกรณ์ Wi-Fi ที่อยู่ใกล้เคียง" + + + + "ข้อมูลบริการชำระเงิน NFC ที่ต้องการ" "อนุญาตให้แอปรับข้อมูลบริการชำระเงิน NFC ที่ต้องการ เช่น รหัสแอป (AID) ที่ลงทะเบียนและปลายทางของเส้นทาง" "ควบคุม Near Field Communication" @@ -2431,54 +2435,30 @@ "เปิด" "ย้อนกลับ" "รอดำเนินการ..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "SOS ดาวเทียมพร้อมใช้งานแล้ว" + "คุณส่งข้อความหาบริการช่วยเหลือฉุกเฉินได้ในกรณีที่ไม่มีเครือข่ายมือถือหรือ Wi-Fi โดยที่แอปรับส่งข้อความเริ่มต้นของคุณจะต้องเป็น Google Messages" + "ไม่รองรับ SOS ดาวเทียม" + "อุปกรณ์นี้ไม่รองรับ SOS ดาวเทียม" + "ไม่ได้ตั้งค่า SOS ดาวเทียม" + "ตรวจสอบว่าคุณได้เชื่อมต่ออินเทอร์เน็ตแล้ว และลองตั้งค่าอีกครั้ง" + "SOS ดาวเทียมไม่พร้อมใช้งาน" + "SOS ดาวเทียมไม่พร้อมใช้งานในประเทศหรือภูมิภาคนี้" + "ไม่ได้ตั้งค่า SOS ดาวเทียม" + "หากต้องการรับส่งข้อความผ่านดาวเทียม ให้ตั้ง Google Messages เป็นแอปรับส่งข้อความเริ่มต้น" + "SOS ดาวเทียมไม่พร้อมใช้งาน" + "หากต้องการตรวจสอบว่า SOS ดาวเทียมพร้อมให้บริการในประเทศหรือภูมิภาคนี้หรือไม่ ให้เปิดการตั้งค่าตำแหน่ง" + "การรับส่งข้อความผ่านดาวเทียมพร้อมใช้งาน" + "คุณรับส่งข้อความผ่านดาวเทียมได้ในกรณีที่ไม่มีเครือข่ายมือถือหรือ Wi-Fi โดยที่แอปรับส่งข้อความเริ่มต้นของคุณจะต้องเป็น Google Messages" + "ไม่รองรับการรับส่งข้อความผ่านดาวเทียม" + "อุปกรณ์นี้ไม่รองรับการรับส่งข้อความผ่านดาวเทียม" + "ไม่ได้ตั้งค่าการรับส่งข้อความผ่านดาวเทียม" + "ตรวจสอบว่าคุณได้เชื่อมต่ออินเทอร์เน็ตแล้ว และลองตั้งค่าอีกครั้ง" + "การรับส่งข้อความผ่านดาวเทียมไม่พร้อมใช้งาน" + "การรับส่งข้อความผ่านดาวเทียมไม่พร้อมใช้งานในประเทศหรือภูมิภาคนี้" + "ไม่ได้ตั้งค่าการรับส่งข้อความผ่านดาวเทียม" + "หากต้องการรับส่งข้อความผ่านดาวเทียม ให้ตั้ง Google Messages เป็นแอปรับส่งข้อความเริ่มต้น" + "การรับส่งข้อความผ่านดาวเทียมไม่พร้อมใช้งาน" + "หากต้องการตรวจสอบว่าการรับส่งข้อความผ่านดาวเทียมพร้อมให้บริการในประเทศหรือภูมิภาคนี้หรือไม่ ให้เปิดการตั้งค่าตำแหน่ง" "ตั้งค่าการปลดล็อกด้วยลายนิ้วมืออีกครั้ง" "ระบบไม่จดจำ %s อีกต่อไป" "ระบบไม่จดจำ %1$s และ %2$s อีกต่อไป" diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index f15d952db15ae7cbc01d8af97a970adc70401355..b44faae4e84ffac0f964aca6a7cbe5ba76c45ca4 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -612,6 +612,10 @@ "Payagan ang app na tukuyin ang relatibong posisyon sa pagitan ng mga kalapit na Ultra-Wideband device" "makipag-ugnayan sa mga kalapit na Wi‑Fi device" "Nagbibigay-daan sa app na i-advertise ang, kumonekta sa, at tukuyin ang nauugnay na posisyon ng mga kalapit na Wi‑Fi device" + + + + "Impormasyon sa Gustong NFC na Serbisyo sa Pagbabayad" "Pinapayagan ang app na makakuha ng impormasyon sa gustong nfc na serbisyo sa pagbabayad tulad ng mga nakarehistrong application ID at destinasyon ng ruta." "kontrolin ang Near Field Communication" @@ -2431,54 +2435,30 @@ "I-on" "Bumalik" "Nakabinbin..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Available na ang SOS gamit ang Satellite" + "Puwede kang magpadala ng mensahe sa mga serbisyong pang-emergency kung walang mobile o Wi-Fi network. Dapat Google Messages ang default mong app sa pagmemensahe." + "Hindi sinusuportahan ang SOS gamit ang satellite" + "Hindi sinusuportahan ang SOS gamit ang satellite sa device na ito" + "Hindi naka-set up ang SOS gamit ang satellite" + "Tiyaking nakakonekta ka sa internet at subukang mag-set up ulit" + "Hindi available ang SOS gamit ang satellite" + "Hindi available ang SOS gamit ang satellite sa bansa o rehiyong ito" + "Hindi naka-set up ang SOS gamit ang satellite" + "Para magpadala ng mensahe sa pamamagitan ng satellite, itakda ang Google Messages bilang iyong default na app sa pagmemensahe" + "Hindi available ang SOS gamit ang satellite" + "Para tingnan kung available ang SOS gamit ang satellite sa bansa o rehiyong ito, i-on ang mga setting ng lokasyon" + "Available ang satellite messaging" + "Puwede kang magpadala ng mensahe sa pamamagitan ng satellite kung walang mobile o Wi-Fi network. Dapat Google Messages ang default mong app sa pagmemensahe." + "Hindi sinusuportahan ang satellite messaging" + "Hindi sinusuportahan ang satellite messaging sa device na ito" + "Hindi naka-set up ang satellite messaging" + "Tiyaking nakakonekta ka sa internet at subukang mag-set up ulit" + "Hindi available ang satellite messaging" + "Hindi available ang satellite messaging sa bansa o rehiyong ito" + "Hindi naka-set up ang satellite messaging" + "Para magpadala ng mensahe sa pamamagitan ng satellite, itakda ang Google Messages bilang iyong default na app sa pagmemensahe" + "Hindi available ang satellite messaging" + "Para tingnan kung available ang satellite messaging sa bansa o rehiyong ito, i-on ang mga setting ng lokasyon" "I-set up ulit ang Pag-unlock Gamit ang Fingerprint" "Hindi na makilala ang %s." "Hindi na makilala ang %1$s at %2$s." @@ -2497,7 +2477,7 @@ "Email" "SMS" "Musika" - "Kalendaryo" + "Calendar" "Calculator" "Mga Mapa" "Mga Application" diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 39a005f628f568a6c88b86c6e3f4d62247a46868..e54b8999a1dce06fc1432022d15098c23f4f4955 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -612,6 +612,10 @@ "Uygulamanın, yakındaki Ultra Geniş Bant cihazların birbirine göre konumunu belirlemesine izin verin" "yakındaki kablosuz cihazlarla etkileşim kur" "Uygulamanın reklam sunmasına, bağlanmasına ve yakındaki kablosuz cihazların göreli konumunu belirlemesine izin verir" + + + + "Tercih Edilen NFC Ödeme Hizmeti Bilgileri" "Uygulamaya, kayıtlı yardımlar ve rota hedefi gibi tercih edilen NFC ödeme hizmeti bilgilerini alma izni verir." "Yakın Alan İletişimini denetle" diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 3b9cd192f7a4c16c39a896524fa07ba8b1f97804..344cb28afcb28071cbe13aa80c9bc60418c754af 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -614,6 +614,10 @@ "З цим дозволом додаток може визначати відстань між розташованими поблизу пристроями з надширокосмуговим зв’язком" "взаємодіяти з пристроями Wi‑Fi поблизу" "Додаток може виявляти пристрої Wi‑Fi поблизу, підключатися до них і визначати їх відносне розташування" + + + + "Використання інформації з платіжного NFC-сервісу" "Дозволяє додатку отримувати доступ до інформації потрібного платіжного NFC-сервісу (наприклад, пов\'язаних ідентифікаторів чи даних про маршрутизацію трансакцій)." "контрол. Near Field Communication" @@ -1575,7 +1579,7 @@ "Видалити елементи" "Відмінити видалення" "Наразі нічого не робити" - "Вибрати обліковий запис" + "Вибрати облік. запис" "Додати обліковий запис" "Додати облік. запис" "Збільшити" diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 679f1026197a94582027f26351fdce7816d5ca72..45e1a2347dbbbefa0afef413dc733f05ab8a9ff0 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -612,6 +612,10 @@ "ایپ کو قریبی الٹرا وائڈ بینڈ آلات کے مابین متعلقہ پوزیشن کا تعین کرنے کی اجازت دیں" "‏قریبی Wi-Fi آلات کے ساتھ تعامل کریں" "‏ایپ کو اشتہار دینے، منسلک کرنے اور قریبی Wi-Fi آلات کی متعلقہ پوزیشن کا تعین کرنے کی اجازت دیتا ہے" + + + + "‏ترجیح شدہ NFC ادائیگی کی سروس کی معلومات" "‏ایپ کو رجسٹرشدہ ایڈز اور روٹ ڈسٹنیشن جیسی ترجیح شدہ nfc ادائیگی سروس کی معلومات حاصل کرنے کی اجازت دیتا ہے۔" "‏Near Field کمیونیکیشن کنٹرول کریں" @@ -2431,54 +2435,30 @@ "آن کریں" "واپس جائیں" "زیر التواء..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "‏سیٹلائٹ SOS اب دستیاب ہے" + "‏موبائل یا Wi-Fi نیٹ ورک نہ ہونے پر آپ ایمرجنسی سروسز کو پیغام بھیج سکتے ہیں۔ ‫Google پیغامات آپ کی ڈیفالٹ پیغام رسانی ایپ ہونی چاہیے۔" + "‏سیٹلائٹ SOS تعاون یافتہ نہیں ہے" + "‏سیٹلائٹ SOS اس آلے پر تعاون یافتہ نہیں ہے" + "‏سیٹلائٹ SOS سیٹ اپ نہیں ہے" + "یقینی بنائیں کہ آپ انٹرنیٹ سے منسلک ہیں اور دوبارہ سیٹ اپ کرنے کی کوشش کریں" + "‏سیٹلائٹ SOS دستیاب نہیں ہے" + "‏سیٹلائٹ SOS اس ملک یا علاقے میں دستیاب نہیں ہے" + "‏سیٹلائٹ SOS سیٹ اپ نہیں ہے" + "‏سیٹلائٹ کے ذریعے پیغام بھیجنے کے لیے، Google پیغامات کو اپنی ڈیفالٹ پیغام رسانی ایپ کے طور پر سیٹ کریں" + "‏سیٹلائٹ SOS دستیاب نہیں ہے" + "‏یہ چیک کرنے کے لیے کہ آیا اس ملک یا علاقے میں سیٹلائٹ SOS دستیاب ہے، مقام کی ترتیبات کو آن کریں" + "سیٹلائٹ پیغام رسانی دستیاب ہے" + "‏موبائل یا Wi-Fi نیٹ ورک نہ ہونے پر آپ سیٹلائٹ کے ذریعے پیغام بھیج سکتے ہیں۔ ‫Google پیغامات آپ کی ڈیفالٹ پیغام رسانی ایپ ہونی چاہیے۔" + "سیٹلائٹ پیغام رسانی تعاون یافتہ نہیں ہے" + "سیٹلائٹ پیغام رسانی اس آلے پر تعاون یافتہ نہیں ہے" + "سیٹلائٹ پیغام رسانی سیٹ اپ نہیں ہے" + "یقینی بنائیں کہ آپ انٹرنیٹ سے منسلک ہیں اور دوبارہ سیٹ اپ کرنے کی کوشش کریں" + "سیٹلائٹ پیغام رسانی دستیاب نہیں ہے" + "سیٹلائٹ پیغام رسانی اس ملک یا علاقے میں دستیاب نہیں ہے" + "سیٹلائٹ پیغام رسانی سیٹ اپ نہیں ہے" + "‏سیٹلائٹ کے ذریعے پیغام بھیجنے کے لیے، Google پیغامات کو اپنی ڈیفالٹ پیغام رسانی ایپ کے طور پر سیٹ کریں" + "سیٹلائٹ پیغام رسانی دستیاب نہیں ہے" + "یہ چیک کرنے کے لیے کہ آیا اس ملک یا علاقے میں سیٹلائٹ پیغام رسانی دستیاب ہے، مقام کی ترتیبات کو آن کریں" "فنگر پرنٹ اَن لاک کو دوبارہ سیٹ اپ کریں" "%s مزید پہچانا نہیں جا سکتا۔" "%1$s اور %2$s کو مزید پہچانا نہیں جا سکتا۔" diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index d7620e1f3afbab05b11f7cda12587e9681a71495..f123bea572b15fa04d08cd0c0e54dba6fb7d3f60 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -612,6 +612,10 @@ "Ilovaga yaqin atrofdagi ultra keng polosali qurilmalarining nisbiy joylashishini aniqlashga ruxsat beradi" "Yaqin-atrofdagi Wi-Fi qurilmalar bilan ishlash" "Ilovaga yaqin-atrofdagi Wi-Fi qurilmalarga reklama yuborish, ulanish va ularning taxminiy joylashuvini aniqlash imkonini beradi." + + + + "Asosiy NFC toʻlov xizmati haqidagi axborot" "Bu ilovaga asosiy NFC toʻlov xizmati haqidagi axborotni olish imkonini beradi (masalan, qayd qilingan AID identifikatorlari va marshrutning yakuniy manzili)." "NFC modulini boshqarish" @@ -2431,54 +2435,30 @@ "Yoqish" "Orqaga" "Kutilmoqda..." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "Sputnik SOS xizmati mavjud" + "Agar mobil yoki Wi-Fi tarmoq boʻlmasa, favqulodda xizmatlarga xabar yuborishingiz mumkin. Google Xabarlar asosiy xabar almashinuv ilovasi boʻlishi zarur." + "Sputnik SOS ishlamaydi" + "Bu qurilmada sputnik SOS ishlamaydi" + "Sputnik SOS sozlanmagan" + "Internetga ulanganingizni tekshiring va qayta sozlang" + "Sputnik SOS mavjud emas" + "Sputnik SOS bu mamlakat yoki hududda mavjud emas" + "Sputnik SOS sozlanmagan" + "Sunʼiy yoʻldosh orqali xabarlashish uchun Google Xabarlar ilovasini asosiy xabar almashinuv ilovasi sifatida sozlang" + "Sputnik SOS mavjud emas" + "Bu mamlakat yoki hududda sputnik SOS mavjudligini tekshirish uchun joylashuv sozlamalarini yoqing" + "Sunʼiy yoʻldosh orqali xabarlashuv mavjud" + "Agar mobil yoki Wi-Fi tarmoq boʻlmasa, sunʼiy yoʻldosh orqali xabar yuborishingiz mumkin. Google Xabarlar asosiy xabar almashinuv ilovasi boʻlishi zarur." + "Sunʼiy yoʻldosh orqali xabarlashuv ishlamaydi" + "Bu qurilmada sunʼiy yoʻldosh orqali xabarlashuv ishlamaydi" + "Sunʼiy yoʻldosh orqali xabarlashuv sozlanmagan" + "Internetga ulanganingizni tekshiring va qayta sozlang" + "Sunʼiy yoʻldosh orqali xabarlashuv mavjud emas" + "Sunʼiy yoʻldosh orqali xabarlashuv ushbu mamlakat yoki hududda ishlamaydi" + "Sunʼiy yoʻldosh orqali xabarlashuv sozlanmagan" + "Sunʼiy yoʻldosh orqali xabarlashish uchun Google Xabarlar ilovasini asosiy xabar almashinuv ilovasi sifatida sozlang" + "Sunʼiy yoʻldosh orqali xabarlashuv mavjud emas" + "Bu mamlakat yoki hududda sunʼiy yoʻldosh orqali xabarlashuv mavjudligini tekshirish uchun joylashuv sozlamalarini yoqing" "Barmoq izi bilan ochish funksiyasini qayta sozlang" "%s endi tanilmaydi." "%1$s va %2$s endi tanilmaydi." diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 663078b5caf4bff6bd9678289b6d4834549c7a4a..ef50a9badc4c18db9c56bfda8ef766edb4de0270 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -612,6 +612,10 @@ "Cho phép ứng dụng xác định khoảng cách tương đối giữa các thiết bị ở gần dùng Băng tần siêu rộng" "tương tác với các thiết bị Wi‑Fi lân cận" "Cho phép ứng dụng này thông báo, kết nối và xác định vị trí tương đối của các thiết bị Wi‑Fi lân cận" + + + + "Thông tin về dịch vụ thanh toán qua công nghệ giao tiếp tầm gần (NFC) được ưu tiên" "Cho phép ứng dụng nhận thông tin về dịch vụ thanh toán qua công nghệ giao tiếp tầm gần mà bạn ưu tiên, chẳng hạn như các hình thức hỗ trợ đã đăng ký và điểm đến trong hành trình." "kiểm soát Liên lạc trường gần" diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index 4d2085bbe0c78ff295e72c5c24bb86c63c1effac..7ac17595a27886a6ef80a846daf87217f8836adc 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -238,16 +238,16 @@ a similar way. @color/system_secondary_fixed_dim @color/system_on_error_container_dark @color/system_on_secondary_fixed - @color/system_on_surface_light + @color/system_on_surface_light @color/system_tertiary_fixed_dim @color/system_on_tertiary_fixed @color/system_primary_fixed_dim @color/system_secondary_container_dark @color/system_error_container_dark @color/system_on_primary_fixed - @color/system_primary_light + @color/system_primary_light @color/system_secondary_fixed - @color/system_surface_light + @color/system_surface_light @color/system_surface_variant_dark @color/system_tertiary_container_dark @color/system_tertiary_fixed diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 6007d1e5f9c5e86280824e58a1669ba8268b9169..e2f66fc93c0846aa41118de822b9df6bb4329d10 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -612,6 +612,10 @@ "允许应用确定附近超宽带设备之间的相对位置" "与附近的 WLAN 设备互动" "允许该应用向附近的 WLAN 设备进行广播、连接到这些设备以及确定这些设备的相对位置" + + + + "首选 NFC 付款服务信息" "允许应用获取首选 NFC 付款服务信息,例如注册的应用标识符和路线目的地。" "控制近距离通信" @@ -2431,54 +2435,30 @@ "开启" "返回" "待归档…" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + "现在支持卫星紧急呼救功能" + "即使没有移动网络或 WLAN 网络,您仍可以向应急服务发送消息。您必须将 Google 信息设为默认的即时通讯应用。" + "不支持卫星紧急呼救功能" + "此设备不支持卫星紧急呼救功能" + "未设置卫星紧急呼救功能" + "请确保您已联网,然后尝试重新设置" + "不支持卫星紧急呼救功能" + "此国家/地区不支持卫星紧急呼救功能" + "未设置卫星紧急呼救功能" + "如需通过卫星发送消息,请将 Google 信息设为默认即时通讯应用" + "不支持卫星紧急呼救功能" + "若要查看此国家/地区是否支持卫星紧急呼救功能,请开启位置信息设置" + "支持卫星消息功能" + "即使没有移动网络或 WLAN 网络,您仍可以通过卫星发送消息。您必须将 Google 信息设为默认的即时通讯应用。" + "不支持卫星消息功能" + "此设备不支持卫星消息功能" + "未设置卫星消息功能" + "请确保您已联网,然后尝试重新设置" + "不支持卫星消息功能" + "此国家/地区不支持卫星消息功能" + "未设置卫星消息功能" + "如需通过卫星发送消息,请将 Google 信息设为默认即时通讯应用" + "不支持卫星消息功能" + "若要查看此国家/地区是否支持卫星消息功能,请开启位置信息设置" "重新设置指纹解锁功能" "系统无法再识别%s。" "系统无法再识别%1$s%2$s。" diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index fcf50e4beec406b0acf6678b93fd73c96d0f2461..a2d32805df76bbc27bc47d267bcef9268d7f9cbf 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -612,6 +612,10 @@ "允許應用程式判斷附近超寬頻裝置之間的相對位置" "與附近的 Wi‑Fi 裝置互動" "允許應用程式向附近的 Wi-Fi 裝置顯示此裝置、連接這些裝置並判斷其相對位置" + + + + "由用戶允許授權的 NFC 付款服務資訊" "允許應用程式取得由用戶允許授權的 NFC 付款服務資訊 (如已註冊的付款輔助功能和最終付款對象)。" "控制近距離無線通訊" diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index e186e7c908b2036ae381026d2160a726faa80971..d1f8b5a289ed67cce927d3c49747d327cfe92275 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -612,6 +612,10 @@ "允許應用程式判斷附近超寬頻裝置間的相對位置" "與鄰近的 Wi-Fi 裝置互動" "允許應用程式顯示鄰近的 Wi-Fi 裝置的資料、與其連線並判斷相對位置" + + + + "首選 NFC 付費服務資訊" "允許應用程式取得首選 NFC 付費服務資訊,例如已註冊的輔助工具和路線目的地。" "控制近距離無線通訊" @@ -640,12 +644,12 @@ "允許應用程式修改你的相片收藏。" "讀取你的媒體收藏的位置資訊" "允許應用程式讀取你的媒體收藏的位置資訊。" - "使用生物特徵辨識功能" - "使用生物特徵辨識或螢幕鎖定功能" + "使用生物辨識功能" + "使用生物辨識或螢幕鎖定功能" "驗證你的身分" - "如要繼續操作,請使用生物特徵辨識功能驗證身分" - "請使用生物特徵辨識或螢幕鎖定功能驗證身分,才能繼續操作" - "無法使用生物特徵辨識硬體" + "如要繼續操作,請使用生物辨識功能驗證身分" + "請使用生物辨識或螢幕鎖定功能驗證身分,才能繼續操作" + "無法使用生物辨識硬體" "已取消驗證" "無法辨識" "無法辨識臉孔" diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 69ec5edc9004224019bc2b7d894a7046f838157d..d1b7e510168e3cb57ef7d1960c9984fd1b6736b8 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -612,6 +612,10 @@ "Vumela i-app inqume indawo ehambelanayo phakathi kwamadivayisi e-Ultra-Wideband aseduze" "xhumana namadivayisi we-Wi‑Fi aseduze" "Ivumela i-app ikhangise, ixhume, futhi inqume isimo esihambisanayo samadivayisi we-Wi-Fi aseduze" + + + + "Ulwazi Lwesevisi Yenkokhelo Ye-NFC Okhethwayo" "Ivuemela uhlelo lokusebenza ukuthola ulwazi lesevisi yenkokhelo ye-nfc njengezinsiza zokubhalisa nezindawo zomzila." "lawula Uxhumano Lwenkambu Eseduze" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e6dedce8feaf4836588e7cf55d7609414a8475d6..f6590b1360f8d98fd1e8bb1a5fe4fc66256023ae 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1218,155 +1218,132 @@ it prevent any 'false' in any of its children. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - + - - - - - - - - - + + + + + + + + + + + + + - + + + + + - + - - - - - - - - - + + + + + + + + + + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + @@ -1376,23 +1353,41 @@ - + - + + + - - - + + + + + + + + + + + + + + + + + + + @@ -7753,14 +7748,14 @@ - - - + + + + + + + - - - - @@ -7771,7 +7766,7 @@ - + @@ -10284,23 +10279,26 @@ + + + + + + + + + + + + + - - - - - - - - - - - + @@ -10309,6 +10307,11 @@ + + + + + - + + + + + + + + + + diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index f5bb554b0b32ce5c99aacc9c4990aa4885affc2b..13dd4a35564c29bd7aeb5fe6a331c38bec31f2fb 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -238,393 +238,319 @@ #F9AB00 - + #FFFFFF - + #FEFBFF - + #EEF0FF - + #D9E2FF - + #B0C6FF - + #94AAE4 - + #7A90C8 - + #6076AC - + #475D92 - + #2F4578 - + #152E60 - + #001945 - + #000000 - - + #FFFFFF - + #FEFBFF - + #EEF0FF - + #DCE2F9 - + #C0C6DC - + #A4ABC1 - + #8A90A5 - + #70778B - + #575E71 - + #404659 - + #2A3042 - + #151B2C - + #000000 - - + #FFFFFF - + #FFFBFF - + #FFEBFA - + #FDD7FA - + #E0BBDD - + #C3A0C1 - + #A886A6 - + #8C6D8C - + #725572 - + #593D59 - + #412742 - + #2A122C - + #000000 - - + #FFFFFF - + #FEFBFF - + #F1F0F7 - + #E2E2E9 - + #C6C6CD - + #ABABB1 - + #909097 - + #76777D - + #5D5E64 - + #45464C - + #2F3036 - + #1A1B20 - + #000000 - - + #FFFFFF - + #FEFBFF - + #F0F0FA - + #E1E2EC - + #C5C6D0 - + #A9ABB4 - + #8F9099 - + #757780 - + #5C5E67 - + #44464F - + #2E3038 - + #191B23 - + #000000 - - - #ffffff - - #FFFBF9 - - #FCEEEE - - #F9DEDC - - #F2B8B5 - - #EC928E - - #E46962 - - #DC362E - - #B3261E - - #8C1D18 - - #601410 - - #410E0B - + + #FFFFFF + + #FFFBFF + + #FFEDEA + + #FFDAD6 + + #FFB4AB + + #FF897D + + #FF5449 + + #DE3730 + + #BA1A1A + + #93000A + + #690005 + + #410002 + #000000 - - #D9E2FF - #001945 - #475D92 + #FAF8FF + #D9E2FF + #000000 + #44464F + #BA1A1A + #FFDAD6 + #F1F0F7 + #B0C6FF + #2F3036 + #1A1B20 + #FFFFFF + #93000A #FFFFFF - #DCE2F9 - #151B2C - #575E71 + #2F4578 #FFFFFF - #FDD7FA - #2A122C - #725572 + #404659 + #1A1B20 + #44464F #FFFFFF - #FAF8FF - #1A1B20 + #593D59 + #757780 + #C5C6D0 + #76777D + #757780 + #6076AC + #70778B + #8C6D8C + #475D92 + #D9E2FF + #000000 + #575E71 + #DCE2F9 + #000000 #FAF8FF - #1A1B20 - #F4F3FA - #FFFFFF + #FAF8FF #EEEDF4 #E8E7EF #E2E2E9 - #FAF8FF + #F4F3FA + #FFFFFF #DAD9E0 + #475D92 #E1E2EC - #44464F - #757780 - #C5C6D0 - #BA1A1A - #FFFFFF - #FFDAD6 - #410002 - #D9E2FF - #44464F - #000000 -#E2E2E9 - #C5C6D0 + #725572 + #FDD7FA + #E2E2E9 + #E2E2E9 #E2E2E9 + #C5C6D0 #E2E2E9 - #E2E2E9 - #6076AC - #70778B - #8C6D8C - #76777D - #757780 - #2F4578 - #D9E2FF - #B0C6FF + #121318 + #2F4578 + #FFFFFF + #C5C6D0 + #FFB4AB + #93000A + #2F3036 + #475D92 + #E2E2E9 + #E2E2E9 + #690005 + #FFDAD6 #152E60 - #404659 - #DCE2F9 - #C0C6DC + #D9E2FF #2A3042 - #593D59 - #FDD7FA - #E0BBDD + #DCE2F9 + #E2E2E9 + #C5C6D0 #412742 - #121318 - #E2E2E9 + #FDD7FA + #8F9099 + #44464F + #76777D + #757780 + #6076AC + #70778B + #8C6D8C + #B0C6FF + #2F4578 + #000000 + #C0C6DC + #404659 + #000000 #121318 - #E2E2E9 - #1A1B20 - #0C0E13 + #38393F #1E1F25 #282A2F #33343A - #38393F + #1A1B20 + #0C0E13 #121318 + #B0C6FF #44464F - #C5C6D0 - #8F9099 - #44464F - #FFB4AB - #690005 - #93000A - #FFDAD6 - #2F4578 - #C5C6D0 - #FFFFFF + #E0BBDD + #593D59 + #1A1B20 #1A1B20 - #44464F #1A1B20 + #44464F #1A1B20 - #1A1B20 - #6076AC - #70778B - #8C6D8C - #76777D - #757780 - #D9E2FF - #B0C6FF #001945 #2F4578 - #DCE2F9 - #C0C6DC #151B2C #404659 - #FDD7FA - #E0BBDD #2A122C #593D59 - - - #EEF0FF - #373D50 - #3D5487 - #4F659A - #D9E2FF - #475D92 - #94AAE4 - #E0BBDD + #D9E2FF + #B0C6FF + #DCE2F9 + #C0C6DC + #FDD7FA + #E0BBDD #475D92 #6E7488 #5E73A9 #8A6A89 - #000000 -#D9E2FF + #373D50 + #3D5487 + #725572 #152E60 #2F4578 - #2F3036 #E1E2EC #C5C6D0 - #0C0E13 + #475D92 #C5C6D0 - #152E60 - #8A90A5 - #D9E2FF - #B0C6FF - #2F4578 - #B0C6FF - #94AAE4 - #FDD7FA + #D9E2FF + #0C0E13 + #2F3036 + #D9E2FF + #94AAE4 + #E0BBDD + #000000 + #4F659A + #EEF0FF #B0C6FF #DCE2F9 #7A90C8 #FDD7FA - #000000 -#D9E2FF + #8A90A5 + #D9E2FF + #FDD7FA #001945 #2F4578 - #2F3036 #E1E2EC #C5C6D0 - #0C0E13 + #B0C6FF #50525A + #D9E2FF + #0C0E13 + #2F3036 + #2F4578 + #94AAE4 + #FDD7FA + #000000 + #B0C6FF + #152E60 #5F6368 diff --git a/core/res/res/values/colors_dynamic.xml b/core/res/res/values/colors_dynamic.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab283eb3c6c845945b2500644eb862269ea26c62 --- /dev/null +++ b/core/res/res/values/colors_dynamic.xml @@ -0,0 +1,103 @@ + + + + + + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light + @color/system_error_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light + @color/system_on_background_light + @color/system_on_error_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light + @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light + @color/system_outline_light + @color/system_outline_variant_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light + @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light + @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light + @color/system_tertiary_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim + @color/system_brand_a_light + @color/system_brand_b_light + @color/system_brand_c_light + @color/system_brand_d_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light + @color/system_on_shade_active_light + @color/system_on_shade_active_variant_light + @color/system_on_shade_inactive_light + @color/system_on_shade_inactive_variant_light + @color/system_on_theme_app_light + @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 219cefd273ac05ffad115b9d4fd4ea9618030849..3f4ea2df9266f6411cc50388676302295d881cf3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1849,6 +1849,10 @@ -1 + + 30000 + + true true + + true + + 0 @@ -5894,6 +5906,11 @@ + + + com.android.server.location.ComprehensiveCountryDetector @@ -6563,7 +6580,7 @@ - 32 + 1024 false @@ -7005,6 +7022,15 @@ + + + + + @@ -7199,9 +7225,12 @@ com\u002eandroid\u002esettings - - + + false + + + diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 31e9913dd98861134b7c9f3b2469be33b91582d0..4ec27a31df8c917d65476c6b7fb0d491a67f4655 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -318,6 +318,12 @@ true + + true + + 16dp - + 52dp + + 72dp + 16dp @@ -310,6 +315,9 @@ 56dp + + 72dp + 49.5dp @@ -331,9 +339,17 @@ 24dp + + 40dp + 4dp + + 8dp + 16dp @@ -803,6 +819,8 @@ 16dp @dimen/notification_icon_circle_size + + @dimen/notification_2025_icon_circle_size @dimen/notification_icon_circle_start @@ -820,13 +838,13 @@ 2dp - 8dp - - 8dp - - 3dp + 4dp 6dp + + 2dp + + 16dp 6dp diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index b6436d0b30a5dd6f3e0a4997a96852dae63b48f7..b0b87d1dead68d32eda938830aee3ed69aef4ddd 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -131,6 +131,8 @@ + + @@ -140,12 +142,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml index 165ff61c7b3e46da58bce01720c06d253892a2af..06843f4b4f76113e455639b80f79e0bf7f0ebc96 100644 --- a/core/res/res/values/stoppable_fgs_system_apps.xml +++ b/core/res/res/values/stoppable_fgs_system_apps.xml @@ -19,6 +19,7 @@ + com.android.virtualization.terminal diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index badb98686fb232cc96e5ee032babe305c4c33d2e..a2a19a23d43193c5bc384115efafdc136ae5f6d6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2098,6 +2098,7 @@ + @@ -2388,6 +2389,8 @@ + + @@ -3139,9 +3142,12 @@ - + + + + @@ -3875,9 +3881,9 @@ - - + + @@ -5310,73 +5316,91 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5678,8 +5702,8 @@ - - + + diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 352c3904406ca77445bcb2cfea92d02b0fe3d96f..d8346d87f62405a763763f76d1a0beca970b0089 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -239,73 +239,90 @@ easier. @color/foreground_device_default_dark @color/foreground_device_default_light - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim + @color/system_brand_a_dark + @color/system_brand_b_dark + @color/system_brand_c_dark + @color/system_brand_d_dark @color/system_clock_hour_dark @color/system_clock_minute_dark @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark - @color/system_brand_a_dark - @color/system_brand_b_dark - @color/system_brand_c_dark - @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @@ -1070,73 +1189,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @@ -1320,73 +1473,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @@ -1554,73 +1741,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark + @color/system_error_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark + @color/system_on_background_dark + @color/system_on_error_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark @color/system_on_secondary_container_dark + @color/system_on_surface_dark + @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark - @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark - @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark - @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark - @color/system_on_surface_variant_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim + @color/system_brand_a_light + @color/system_brand_b_light + @color/system_brand_c_light + @color/system_brand_d_light @color/system_clock_hour_light @color/system_clock_minute_light @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light - @color/system_brand_a_light - @color/system_brand_b_light - @color/system_brand_c_light - @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -2379,73 +2685,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark @@ -2747,73 +3087,90 @@ easier. ?attr/colorBackgroundFloating ?attr/colorBackgroundFloating - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -2980,73 +3354,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light + @color/system_error_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light + @color/system_on_background_light + @color/system_on_error_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light @color/system_on_secondary_container_light + @color/system_on_surface_light + @color/system_on_surface_variant_light + @color/system_on_tertiary_light @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light - @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light - @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light - @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light - @color/system_on_surface_variant_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -3697,73 +4173,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -3918,73 +4428,90 @@ easier. @color/foreground_device_default_light @color/foreground_device_default_dark - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim + @color/system_brand_a_light + @color/system_brand_b_light + @color/system_brand_c_light + @color/system_brand_d_light @color/system_clock_hour_light @color/system_clock_minute_light @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light - @color/system_brand_a_light - @color/system_brand_b_light - @color/system_brand_c_light - @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -4019,73 +4546,90 @@ easier. @color/foreground_device_default_light @color/foreground_device_default_dark - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -4379,73 +4957,90 @@ easier. @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @style/Widget.DeviceDefault.Toolbar - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_light - @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light @color/system_on_background_light - @color/system_primary_fixed - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light + @color/system_on_secondary_container_light + @color/system_on_surface_light @color/system_on_surface_variant_light + @color/system_on_tertiary_light + @color/system_on_tertiary_container_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light @@ -4968,74 +5631,90 @@ easier. @color/list_divider_color_light @color/list_divider_opacity_device_default_light - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_light - @color/system_on_primary_fixed_variant + @color/system_background_light + @color/system_control_activated_light + @color/system_control_highlight_light + @color/system_control_normal_light + @color/system_error_light + @color/system_error_container_light + @color/system_inverse_on_surface_light + @color/system_inverse_primary_light + @color/system_inverse_surface_light + @color/system_on_background_light + @color/system_on_error_light + @color/system_on_error_container_light + @color/system_on_primary_light + @color/system_on_primary_container_light + @color/system_on_secondary_light @color/system_on_secondary_container_light + @color/system_on_surface_light + @color/system_on_surface_variant_light + @color/system_on_tertiary_light @color/system_on_tertiary_container_light - @color/system_surface_container_low_light - @color/system_on_primary_container_light - @color/system_secondary_fixed_dim - @color/system_on_error_container_light - @color/system_on_secondary_fixed - @color/system_on_surface_dark - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_light - @color/system_error_container_light - @color/system_on_primary_fixed - @color/system_primary_dark - @color/system_secondary_fixed - @color/system_surface_dark - @color/system_surface_variant_light - @color/system_tertiary_container_light - @color/system_tertiary_fixed - @color/system_primary_container_light - @color/system_on_background_light - @color/system_primary_fixed - - @color/system_on_secondary_light - @color/system_on_tertiary_light - @color/system_surface_dim_light - @color/system_surface_bright_light - @color/system_on_error_light - @color/system_surface_light - @color/system_surface_container_high_light - @color/system_surface_container_highest_light - @color/system_on_surface_variant_light @color/system_outline_light @color/system_outline_variant_light - @color/system_on_primary_light - @color/system_on_surface_light - @color/system_surface_container_light + @color/system_palette_key_color_neutral_light + @color/system_palette_key_color_neutral_variant_light + @color/system_palette_key_color_primary_light + @color/system_palette_key_color_secondary_light + @color/system_palette_key_color_tertiary_light @color/system_primary_light + @color/system_primary_container_light + @color/system_scrim_light @color/system_secondary_light + @color/system_secondary_container_light + @color/system_shadow_light + @color/system_surface_light + @color/system_surface_bright_light + @color/system_surface_container_light + @color/system_surface_container_high_light + @color/system_surface_container_highest_light + @color/system_surface_container_low_light + @color/system_surface_container_lowest_light + @color/system_surface_dim_light + @color/system_surface_tint_light + @color/system_surface_variant_light @color/system_tertiary_light - @color/system_error_light - - @color/system_widget_background_light - @color/system_clock_hour_light - @color/system_clock_minute_light - @color/system_clock_second_light - @color/system_theme_app_light - @color/system_on_theme_app_light - @color/system_theme_app_ring_light - @color/system_theme_notif_light + @color/system_tertiary_container_light + @color/system_text_hint_inverse_light + @color/system_text_primary_inverse_light + @color/system_text_primary_inverse_disable_only_light + @color/system_text_secondary_and_tertiary_inverse_light + @color/system_text_secondary_and_tertiary_inverse_disabled_light + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_light @color/system_brand_b_light @color/system_brand_c_light @color/system_brand_d_light - @color/system_under_surface_light - @color/system_shade_active_light + @color/system_clock_hour_light + @color/system_clock_minute_light + @color/system_clock_second_light @color/system_on_shade_active_light @color/system_on_shade_active_variant_light - @color/system_shade_inactive_light @color/system_on_shade_inactive_light @color/system_on_shade_inactive_variant_light - @color/system_shade_disabled_light + @color/system_on_theme_app_light @color/system_overview_background_light + @color/system_shade_active_light + @color/system_shade_disabled_light + @color/system_shade_inactive_light + @color/system_theme_app_light + @color/system_theme_app_ring_light + @color/system_theme_notif_light + @color/system_under_surface_light + @color/system_weather_temp_light + @color/system_widget_background_light - - - + + + @@ -5946,73 +6759,90 @@ easier. @color/system_secondary_dark @color/system_tertiary_dark - @color/system_on_secondary_fixed_variant - @color/system_on_tertiary_fixed_variant - @color/system_surface_container_lowest_dark - @color/system_on_primary_fixed_variant - @color/system_on_secondary_container_dark - @color/system_on_tertiary_container_dark - @color/system_surface_container_low_dark - @color/system_on_primary_container_dark - @color/system_secondary_fixed_dim - @color/system_on_error_container_dark - @color/system_on_secondary_fixed - @color/system_on_surface_light - @color/system_tertiary_fixed_dim - @color/system_on_tertiary_fixed - @color/system_primary_fixed_dim - @color/system_secondary_container_dark + @color/system_background_dark + @color/system_control_activated_dark + @color/system_control_highlight_dark + @color/system_control_normal_dark + @color/system_error_dark @color/system_error_container_dark - @color/system_on_primary_fixed - @color/system_primary_light - @color/system_secondary_fixed - @color/system_surface_light - @color/system_surface_variant_dark - @color/system_tertiary_container_dark - @color/system_tertiary_fixed - @color/system_primary_container_dark + @color/system_inverse_on_surface_dark + @color/system_inverse_primary_dark + @color/system_inverse_surface_dark @color/system_on_background_dark - @color/system_primary_fixed - @color/system_on_secondary_dark - @color/system_on_tertiary_dark - @color/system_surface_dim_dark - @color/system_surface_bright_dark @color/system_on_error_dark - @color/system_surface_dark - @color/system_surface_container_high_dark - @color/system_surface_container_highest_dark + @color/system_on_error_container_dark + @color/system_on_primary_dark + @color/system_on_primary_container_dark + @color/system_on_secondary_dark + @color/system_on_secondary_container_dark + @color/system_on_surface_dark @color/system_on_surface_variant_dark + @color/system_on_tertiary_dark + @color/system_on_tertiary_container_dark @color/system_outline_dark @color/system_outline_variant_dark - @color/system_on_primary_dark - @color/system_on_surface_dark - @color/system_surface_container_dark + @color/system_palette_key_color_neutral_dark + @color/system_palette_key_color_neutral_variant_dark + @color/system_palette_key_color_primary_dark + @color/system_palette_key_color_secondary_dark + @color/system_palette_key_color_tertiary_dark @color/system_primary_dark + @color/system_primary_container_dark + @color/system_scrim_dark @color/system_secondary_dark + @color/system_secondary_container_dark + @color/system_shadow_dark + @color/system_surface_dark + @color/system_surface_bright_dark + @color/system_surface_container_dark + @color/system_surface_container_high_dark + @color/system_surface_container_highest_dark + @color/system_surface_container_low_dark + @color/system_surface_container_lowest_dark + @color/system_surface_dim_dark + @color/system_surface_tint_dark + @color/system_surface_variant_dark @color/system_tertiary_dark - @color/system_error_dark - - @color/system_widget_background_dark - @color/system_clock_hour_dark - @color/system_clock_minute_dark - @color/system_clock_second_dark - @color/system_theme_app_dark - @color/system_on_theme_app_dark - @color/system_theme_app_ring_dark - @color/system_theme_notif_dark + @color/system_tertiary_container_dark + @color/system_text_hint_inverse_dark + @color/system_text_primary_inverse_dark + @color/system_text_primary_inverse_disable_only_dark + @color/system_text_secondary_and_tertiary_inverse_dark + @color/system_text_secondary_and_tertiary_inverse_disabled_dark + @color/system_on_primary_fixed + @color/system_on_primary_fixed_variant + @color/system_on_secondary_fixed + @color/system_on_secondary_fixed_variant + @color/system_on_tertiary_fixed + @color/system_on_tertiary_fixed_variant + @color/system_primary_fixed + @color/system_primary_fixed_dim + @color/system_secondary_fixed + @color/system_secondary_fixed_dim + @color/system_tertiary_fixed + @color/system_tertiary_fixed_dim @color/system_brand_a_dark @color/system_brand_b_dark @color/system_brand_c_dark @color/system_brand_d_dark - @color/system_under_surface_dark - @color/system_shade_active_dark + @color/system_clock_hour_dark + @color/system_clock_minute_dark + @color/system_clock_second_dark @color/system_on_shade_active_dark @color/system_on_shade_active_variant_dark - @color/system_shade_inactive_dark @color/system_on_shade_inactive_dark @color/system_on_shade_inactive_variant_dark - @color/system_shade_disabled_dark + @color/system_on_theme_app_dark @color/system_overview_background_dark + @color/system_shade_active_dark + @color/system_shade_disabled_dark + @color/system_shade_inactive_dark + @color/system_theme_app_dark + @color/system_theme_app_ring_dark + @color/system_theme_notif_dark + @color/system_under_surface_dark + @color/system_weather_temp_dark + @color/system_widget_background_dark \ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml index 7c9d1a47b7efe636771cf82943eb8d7b1f144185..f7c9aac686295f52ba9eb6b288f18cb8246ec450 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml @@ -18,7 +18,7 @@ \ No newline at end of file diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index 5ceee6d09978af58fcb4c817516205f8f0f9b585..088bef2303741a4da4c1357a150d4694466169ff 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -22,11 +22,13 @@ import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.PreferenceScreenRegistry +import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale /** API to get preference graph. */ -abstract class GetPreferenceGraphApiHandler : - ApiHandler { +abstract class GetPreferenceGraphApiHandler( + private val preferenceScreenProviders: Set> +) : ApiHandler { override val requestCodec: MessageCodec get() = GetPreferenceGraphRequestCodec @@ -40,14 +42,16 @@ abstract class GetPreferenceGraphApiHandler : callingUid: Int, request: GetPreferenceGraphRequest, ): PreferenceGraphProto { - val builderRequest = - if (request.screenKeys.isEmpty()) { - val keys = PreferenceScreenRegistry.preferenceScreens.keys - GetPreferenceGraphRequest(keys, request.visitedScreens, request.locale) - } else { - request + val builder = PreferenceGraphBuilder.of(application, request) + if (request.screenKeys.isEmpty()) { + for (key in PreferenceScreenRegistry.preferenceScreens.keys) { + builder.addPreferenceScreenFromRegistry(key) } - return PreferenceGraphBuilder.of(application, builderRequest).build() + for (provider in preferenceScreenProviders) { + builder.addPreferenceScreenProvider(provider) + } + } + return builder.build() } } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 2256bb38dd2c15c44e0b03c5cce554007ddf4917..6760e72be4a68bd42184b3822f0663645c8e3f94 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -133,7 +133,7 @@ private constructor(private val context: Context, private val request: GetPrefer null } - private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { + suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { val metadata = PreferenceScreenRegistry[key] ?: return false return addPreferenceScreenMetadata(metadata) } @@ -146,7 +146,7 @@ private constructor(private val context: Context, private val request: GetPrefer } } - private suspend fun addPreferenceScreenProvider(activityClass: Class<*>) { + suspend fun addPreferenceScreenProvider(activityClass: Class<*>) { Log.d(TAG, "add $activityClass") createPreferenceScreen { activityClass.newInstance() } ?.let { diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt index 6e4db1d904848e2be79f5c31639a2cfdee4d70d3..7cfce0d85cd4e329d7a2a2a0e67f4fa9f5f93946 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt @@ -22,6 +22,7 @@ import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.ipc.ApiDescriptor import com.android.settingslib.ipc.ApiHandler +import com.android.settingslib.ipc.ApiPermissionChecker import com.android.settingslib.ipc.IntMessageCodec import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.BooleanValue @@ -45,7 +46,11 @@ data class PreferenceSetterRequest( PreferenceSetterResult.OK, PreferenceSetterResult.UNSUPPORTED, PreferenceSetterResult.DISABLED, + PreferenceSetterResult.RESTRICTED, PreferenceSetterResult.UNAVAILABLE, + PreferenceSetterResult.REQUIRE_APP_PERMISSION, + PreferenceSetterResult.REQUIRE_USER_AGREEMENT, + PreferenceSetterResult.DISALLOW, PreferenceSetterResult.INVALID_REQUEST, PreferenceSetterResult.INTERNAL_ERROR, ) @@ -87,14 +92,17 @@ class PreferenceSetterApiDescriptor(override val id: Int) : } /** Preference setter API implementation. */ -class PreferenceSetterApiHandler(override val id: Int) : ApiHandler { +class PreferenceSetterApiHandler( + override val id: Int, + private val permissionChecker: ApiPermissionChecker, +) : ApiHandler { override fun hasPermission( application: Application, myUid: Int, callingUid: Int, request: PreferenceSetterRequest, - ): Boolean = true + ) = permissionChecker.hasPermission(application, myUid, callingUid, request) override suspend fun invoke( application: Application, diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt index d9b9590f60e2865f63dcf771e24108e4c6b9015a..1bda277f20187b327598964a290e3302e2dd20d2 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoConverters.kt @@ -16,8 +16,10 @@ package com.android.settingslib.graph +import android.content.ComponentName import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import com.android.settingslib.graph.proto.BundleProto import com.android.settingslib.graph.proto.BundleProto.BundleValue @@ -42,6 +44,20 @@ fun Intent.toProto(): IntentProto = intentProto { this@toProto.type?.let { mimeType = it } } +fun IntentProto.toIntent(): Intent? { + if (!hasComponent()) return null + val componentName = ComponentName.unflattenFromString(component) ?: return null + val intent = Intent() + intent.component = componentName + if (hasAction()) intent.action = action + if (hasData()) intent.data = Uri.parse(data) + if (hasPkg()) intent.`package` = pkg + if (hasFlags()) intent.flags = flags + if (hasExtras()) intent.putExtras(extras.toBundle()) + if (hasMimeType()) intent.setType(mimeType) + return intent +} + fun Bundle.toProto(): BundleProto = bundleProto { fun toProto(value: Any): BundleValue = bundleValueProto { when (value) { @@ -61,14 +77,18 @@ fun Bundle.toProto(): BundleProto = bundleProto { } } -fun BundleValue.stringify(): String = - when { - hasBooleanValue() -> "$valueCase" - hasBytesValue() -> "$bytesValue" - hasIntValue() -> "$intValue" - hasLongValue() -> "$longValue" - hasStringValue() -> stringValue - hasDoubleValue() -> "$doubleValue" - hasBundleValue() -> "$bundleValue" - else -> "Unknown" +fun BundleProto.toBundle(): Bundle = + Bundle().apply { + for ((key, value) in valuesMap) { + when { + value.hasBooleanValue() -> putBoolean(key, value.booleanValue) + value.hasBytesValue() -> putByteArray(key, value.bytesValue.toByteArray()) + value.hasIntValue() -> putInt(key, value.intValue) + value.hasLongValue() -> putLong(key, value.longValue) + value.hasStringValue() -> putString(key, value.stringValue) + value.hasDoubleValue() -> putDouble(key, value.doubleValue) + value.hasBundleValue() -> putBundle(key, value.bundleValue.toBundle()) + else -> throw IllegalArgumentException("Unknown type: ${value.javaClass} $value") + } + } } diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt index 802141dae7ecd6e8e59c5d62bc8cfcf088780070..4febd89a7da42035520db430ce94c9c08cf3c354 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt @@ -56,6 +56,27 @@ interface ApiDescriptor { val responseCodec: MessageCodec } +/** Permission checker for api. */ +fun interface ApiPermissionChecker { + /** + * Returns if the request is permitted. + * + * @param application application context + * @param myUid uid of current process + * @param callingUid uid of peer process + * @param request API request + * @return `false` if permission is denied, otherwise `true` + */ + fun hasPermission(application: Application, myUid: Int, callingUid: Int, request: R): Boolean + + companion object { + private val ALWAYS_ALLOW = ApiPermissionChecker { _, _, _, _ -> true } + + @Suppress("UNCHECKED_CAST") + fun alwaysAllow(): ApiPermissionChecker = ALWAYS_ALLOW as ApiPermissionChecker + } +} + /** * Handler of API. * @@ -64,18 +85,8 @@ interface ApiDescriptor { * * The implementation must be threadsafe. */ -interface ApiHandler : ApiDescriptor { - /** - * Returns if the request is permitted. - * - * @return `false` if permission is denied, otherwise `true` - */ - fun hasPermission( - application: Application, - myUid: Int, - callingUid: Int, - request: Request, - ): Boolean +interface ApiHandler : + ApiDescriptor, ApiPermissionChecker { /** * Invokes the API. diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt index 7ffefed239a46e9aed769011e46a40fbfc9ce6ff..ef907e17d824aa180787a7cd7500d0840958db83 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt @@ -320,6 +320,11 @@ constructor( } } + override fun onNullBinding(name: ComponentName) { + Log.i(TAG, "onNullBinding $name") + close(ClientBindServiceException(null)) + } + internal open fun drainPendingRequests() { disposableHandle = null if (pendingRequests.isEmpty()) { diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt index bde4217b396278cc09cc7a7e38104b7d063b3416..a2b826a50e5874b9cb211866262a180c926e632f 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt @@ -166,10 +166,6 @@ class PreferenceHierarchy internal constructor(metadata: PreferenceMetadata) : } return null } - - /** Returns all the [PreferenceHierarchyNode]s appear in the hierarchy. */ - fun getAllPreferences(): List = - mutableListOf().apply { forEachRecursively { add(it) } } } /** diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 49acc1d44144a0bea335672725c34c8b4f5f5e77..6b7be91c19030c7080122fa7f42c8d0a03a8d20f 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -108,6 +108,9 @@ interface PreferenceBinding { } } +/** Interface indicates that a virtual [Preference] should be created for binding. */ +interface PreferenceBindingPlaceholder + /** Abstract preference screen to provide preference hierarchy and binding factory. */ interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenProvider { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt index fbe892710d4078cbafca9829e6437f2cafb37ae5..cfe6089169d37ec90e8b01637ce7b3d7751d5f79 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt @@ -218,34 +218,47 @@ class PreferenceScreenBindingHelper( preferenceScreen: PreferenceScreen, preferenceBindingFactory: PreferenceBindingFactory, preferenceHierarchy: PreferenceHierarchy, - ) = - preferenceScreen.bindRecursively( - preferenceBindingFactory, - preferenceHierarchy.getAllPreferences().associateBy { it.metadata.key }, - ) - - private fun PreferenceGroup.bindRecursively( - preferenceBindingFactory: PreferenceBindingFactory, - preferences: Map, - storages: MutableMap = mutableMapOf(), ) { - preferences[key]?.let { preferenceBindingFactory.bind(this, it) } - val count = preferenceCount - for (index in 0 until count) { - val preference = getPreference(index) - if (preference is PreferenceGroup) { - preference.bindRecursively(preferenceBindingFactory, preferences, storages) - } else { - preferences[preference.key]?.let { - val metadata = it.metadata - (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> - preference.preferenceDataStore = - storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + val preferences = mutableMapOf() + preferenceHierarchy.forEachRecursively { + val metadata = it.metadata + preferences[metadata.key] = it + } + val storages = mutableMapOf() + + fun Preference.setPreferenceDataStore(metadata: PreferenceMetadata) { + (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage -> + preferenceDataStore = + storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) } + } + } + + fun PreferenceGroup.bindRecursively() { + preferences.remove(key)?.let { preferenceBindingFactory.bind(this, it) } + val count = preferenceCount + for (index in 0 until count) { + val preference = getPreference(index) + if (preference is PreferenceGroup) { + preference.bindRecursively() + } else { + preferences.remove(preference.key)?.let { + preference.setPreferenceDataStore(it.metadata) + preferenceBindingFactory.bind(preference, it) } - preferenceBindingFactory.bind(preference, it) } } } + + preferenceScreen.bindRecursively() + for (node in preferences.values) { + val metadata = node.metadata + val binding = preferenceBindingFactory.getPreferenceBinding(metadata) + if (binding !is PreferenceBindingPlaceholder) continue + val preference = binding.createWidget(preferenceScreen.context) + preference.setPreferenceDataStore(metadata) + preferenceBindingFactory.bind(preference, node, binding) + preferenceScreen.addPreference(preference) + } } } } diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt index 6e38df11156f8fdba70dcc827bdb5fbd50d1d4ca..1823ba641775122460a6c2ff84c95cbfc540894b 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt @@ -19,9 +19,14 @@ package com.android.settingslib.service import android.app.Application import com.android.settingslib.graph.GetPreferenceGraphApiHandler import com.android.settingslib.graph.GetPreferenceGraphRequest +import com.android.settingslib.ipc.ApiPermissionChecker +import com.android.settingslib.preference.PreferenceScreenProvider /** Api to get preference graph. */ -internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() { +internal class PreferenceGraphApi( + preferenceScreenProviders: Set>, + private val permissionChecker: ApiPermissionChecker, +) : GetPreferenceGraphApiHandler(preferenceScreenProviders) { override val id: Int get() = API_GET_PREFERENCE_GRAPH @@ -31,5 +36,5 @@ internal class PreferenceGraphApi : GetPreferenceGraphApiHandler() { myUid: Int, callingUid: Int, request: GetPreferenceGraphRequest, - ) = true + ) = permissionChecker.hasPermission(application, myUid, callingUid, request) } diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt index 8ebb14522c3dae3289b74fccf361c65e38b732f0..ed748bb58a9a239506ef6ee912e351ef9f1f79f6 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt @@ -16,10 +16,14 @@ package com.android.settingslib.service +import com.android.settingslib.graph.GetPreferenceGraphRequest import com.android.settingslib.graph.PreferenceSetterApiHandler +import com.android.settingslib.graph.PreferenceSetterRequest import com.android.settingslib.ipc.ApiHandler +import com.android.settingslib.ipc.ApiPermissionChecker import com.android.settingslib.ipc.MessengerService import com.android.settingslib.ipc.PermissionChecker +import com.android.settingslib.preference.PreferenceScreenProvider /** * Preference service providing a bunch of APIs. @@ -28,14 +32,21 @@ import com.android.settingslib.ipc.PermissionChecker * [PREFERENCE_SERVICE_ACTION]. */ open class PreferenceService( - permissionChecker: PermissionChecker, name: String = "PreferenceService", + permissionChecker: PermissionChecker = PermissionChecker { _, _, _ -> true }, + preferenceScreenProviders: Set> = setOf(), + graphPermissionChecker: ApiPermissionChecker? = null, + setterPermissionChecker: ApiPermissionChecker? = null, + vararg apiHandlers: ApiHandler<*, *>, ) : MessengerService( - listOf>( - PreferenceGraphApi(), - PreferenceSetterApiHandler(API_PREFERENCE_SETTER), - ), + mutableListOf>().apply { + graphPermissionChecker?.let { add(PreferenceGraphApi(preferenceScreenProviders, it)) } + setterPermissionChecker?.let { + add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it)) + } + addAll(apiHandlers) + }, permissionChecker, name, ) diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt index 1f38a6678eae943df4f22579a558053c32ad7ffa..7655daa6cf213b8dfc5374271bdcc775202fbb52 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/ServiceApiConstants.kt @@ -18,5 +18,14 @@ package com.android.settingslib.service const val PREFERENCE_SERVICE_ACTION = "com.android.settingslib.PREFERENCE_SERVICE" +/** API id for retrieving preference graph. */ internal const val API_GET_PREFERENCE_GRAPH = 1 + +/** API id for preference value setter. */ internal const val API_PREFERENCE_SETTER = 2 + +/** + * The max API id reserved for internal preference service usages. Custom API id should start with + * **1000** to avoid conflict. + */ +internal const val API_MAX_RESERVED = 999 diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml index b46181e20eaa842dac055d1103cdcd988f50233b..8b574aa951760b809bed23c0aa860a7451ce5a3a 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + \ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 313748d8f091f346403362008d0517c8c84a9457..46ec62e7a5efc4b7c03b26c864a7af01618ac318 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -51,7 +51,7 @@ @android:color/system_accent1_100 - + @android:color/system_neutral1_800 diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 94ff02d898db2685d946706f3d441685f4cb566f..84a3ed68af01281b4728f7dd76134d0eb71bea7e 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -53,11 +53,11 @@ @android:color/system_surface_container_low_dark @android:color/system_on_primary_container_dark @android:color/system_on_error_container_dark - @android:color/system_on_surface_light + @android:color/system_on_surface_light @android:color/system_secondary_container_dark @android:color/system_error_container_dark - @android:color/system_primary_light - @android:color/system_surface_light + @android:color/system_primary_light + @android:color/system_surface_light @android:color/system_surface_variant_dark @android:color/system_tertiary_container_dark @android:color/system_primary_container_dark diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml index b99ee51234914ab543baaabb2a29de9011fdfb98..fef92b792bec04093c36592bb17f716adc9508e6 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml @@ -67,9 +67,9 @@ @android:color/system_accent1_600 @android:color/system_accent2_100 - + @android:color/system_neutral1_900 - + @android:color/system_neutral1_50 @android:color/system_neutral1_900 diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml index 8b9501608000c3656220903343a64c5c3c9a2e81..90c19e1aa67695ac30f50ff0a3048d8c5a16082a 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -66,16 +66,16 @@ @android:color/system_secondary_fixed_dim @android:color/system_on_error_container_light @android:color/system_on_secondary_fixed - @android:color/system_on_surface_dark + @android:color/system_on_surface_dark @android:color/system_tertiary_fixed_dim @android:color/system_on_tertiary_fixed @android:color/system_primary_fixed_dim @android:color/system_secondary_container_light @android:color/system_error_container_light @android:color/system_on_primary_fixed - @android:color/system_primary_dark + @android:color/system_primary_dark @android:color/system_secondary_fixed - @android:color/system_surface_dark + @android:color/system_surface_dark @android:color/system_surface_variant_light @android:color/system_tertiary_container_light @android:color/system_tertiary_fixed diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt index 4d3a78a583fc55257fce98682ebe7efbc7168759..f2bc380a93de62f1d5e704dab4a2d70e55734692 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt @@ -17,8 +17,12 @@ package com.android.settingslib.spa.gallery.ui import android.os.Bundle +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider @@ -30,6 +34,7 @@ import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Category +import com.android.settingslib.spa.widget.ui.LazyCategory private const val TITLE = "Sample Category" @@ -65,7 +70,7 @@ object CategoryPageProvider : SettingsPageProvider { ) entryList.add( SettingsEntryBuilder.create("Preference 3", owner) - .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") } + .setMacro { SimplePreferenceMacro(title = "Preference 3", summary = "Summary 3") } .build() ) entryList.add( @@ -88,6 +93,13 @@ object CategoryPageProvider : SettingsPageProvider { entries[2].UiLayout() entries[3].UiLayout() } + Column(Modifier.height(200.dp)) { + LazyCategory( + list = entries, + entry = { index: Int -> @Composable { entries[index].UiLayout() } }, + title = { index: Int -> if (index == 0 || index == 2) "LazyCategory" else null }, + ) {} + } } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt index 66680fa547b157745cf3e0efab3dad5aabc3fbe3..28b2b4ab1662fcd2ccb878310cdd1f8badb6d2ff 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -19,8 +19,13 @@ package com.android.settingslib.spa.widget.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material3.MaterialTheme @@ -34,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsShape @@ -98,6 +104,57 @@ fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) } } +/** + * A container that is used to group items with lazy loading. + * + * @param list The list of items to display. + * @param entry The entry for each list item according to its index in list. + * @param key Optional. The key for each item in list to provide unique item identifiers, making + * the list more efficient. + * @param title Optional. Category title for each item or each group of items in the list. It + * should be decided by the index. + * @param bottomPadding Optional. Bottom outside padding of the category. + * @param state Optional. State of LazyList. + * @param content Optional. Content to be shown at the top of the category. + */ + +@Composable +fun LazyCategory( + list: List, + entry: (Int) -> @Composable () -> Unit, + key: ((Int) -> Any)? = null, + title: ((Int) -> String?)? = null, + bottomPadding: Dp = SettingsDimension.paddingSmall, + state: LazyListState = rememberLazyListState(), + content: @Composable () -> Unit, +) { + Column( + Modifier.padding( + PaddingValues( + start = SettingsDimension.paddingLarge, + end = SettingsDimension.paddingLarge, + top = SettingsDimension.paddingSmall, + bottom = bottomPadding, + ) + ) + .clip(SettingsShape.CornerMedium2) + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny), + state = state, + ) { + item { content() } + + items(count = list.size, key = key) { + title?.invoke(it)?.let { title -> CategoryTitle(title) } + val entryPreference = entry(it) + entryPreference() + } + } + } +} + @Preview @Composable private fun CategoryPreview() { diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt index 09a6e6ddc7f0718e05f1bbb712c6104c7dc06a37..4b4a8c20b39e4f0d9690d3c263b2e88ba3a927dd 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt @@ -16,10 +16,16 @@ package com.android.settingslib.spa.widget.ui +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -30,14 +36,11 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class CategoryTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() @Test fun categoryTitle() { - composeTestRule.setContent { - CategoryTitle(title = "CategoryTitle") - } + composeTestRule.setContent { CategoryTitle(title = "CategoryTitle") } composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed() } @@ -46,12 +49,14 @@ class CategoryTest { fun category_hasContent_titleDisplayed() { composeTestRule.setContent { Category(title = "CategoryTitle") { - Preference(remember { - object : PreferenceModel { - override val title = "Some Preference" - override val summary = { "Some summary" } + Preference( + remember { + object : PreferenceModel { + override val title = "Some Preference" + override val summary = { "Some summary" } + } } - }) + ) } } @@ -60,10 +65,45 @@ class CategoryTest { @Test fun category_noContent_titleNotDisplayed() { - composeTestRule.setContent { - Category(title = "CategoryTitle") {} - } + composeTestRule.setContent { Category(title = "CategoryTitle") {} } composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist() } + + @Test + fun lazyCategory_content_displayed() { + composeTestRule.setContent { TestLazyCategory() } + + composeTestRule.onNodeWithText("text").assertExists() + } + + @Test + fun lazyCategory_title_displayed() { + composeTestRule.setContent { TestLazyCategory() } + + composeTestRule.onNodeWithText("LazyCategory 0").assertExists() + composeTestRule.onNodeWithText("LazyCategory 1").assertDoesNotExist() + } +} + +@Composable +private fun TestLazyCategory() { + val list: List = + listOf( + object : PreferenceModel { + override val title = "title" + }, + object : PreferenceModel { + override val title = "title" + }, + ) + Column(Modifier.height(200.dp)) { + LazyCategory( + list = list, + entry = { index: Int -> @Composable { Preference(list[index]) } }, + title = { index: Int -> if (index == 0) "LazyCategory $index" else null }, + ) { + Text("text") + } + } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index bededf03a0f4acd6a0508351a8e338f94b1ddf45..2a214b6f74a6393097494196cc8c00cec1a0b1fb 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -37,7 +37,9 @@ import com.android.settingslib.spa.framework.compose.LifecycleEffect import com.android.settingslib.spa.framework.compose.LogCompositions import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled import com.android.settingslib.spa.widget.ui.CategoryTitle +import com.android.settingslib.spa.widget.ui.LazyCategory import com.android.settingslib.spa.widget.ui.PlaceholderTitle import com.android.settingslib.spa.widget.ui.Spinner import com.android.settingslib.spa.widget.ui.SpinnerOption @@ -55,19 +57,14 @@ import kotlinx.coroutines.flow.MutableStateFlow private const val TAG = "AppList" private const val CONTENT_TYPE_HEADER = "header" -/** - * The config used to load the App List. - */ +/** The config used to load the App List. */ data class AppListConfig( val userIds: List, val showInstantApps: Boolean, val matchAnyUserForAdmin: Boolean, ) -data class AppListState( - val showSystem: () -> Boolean, - val searchQuery: () -> String, -) +data class AppListState(val showSystem: () -> Boolean, val searchQuery: () -> String) data class AppListInput( val config: AppListConfig, @@ -90,7 +87,7 @@ fun AppListInput.AppList() { @Composable internal fun AppListInput.AppListImpl( - viewModelSupplier: @Composable () -> IAppListViewModel, + viewModelSupplier: @Composable () -> IAppListViewModel ) { LogCompositions(TAG, config.userIds.toString()) val viewModel = viewModelSupplier() @@ -125,7 +122,7 @@ private fun AppListModel.AppListWidget( appListData: State?>, header: @Composable () -> Unit, bottomPadding: Dp, - noItemMessage: String? + noItemMessage: String?, ) { val timeMeasurer = rememberTimeMeasurer(TAG) appListData.value?.let { (list, option) -> @@ -135,40 +132,61 @@ private fun AppListModel.AppListWidget( PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications)) return } - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = rememberLazyListStateAndHideKeyboardWhenStartScroll(), - contentPadding = PaddingValues(bottom = bottomPadding), - ) { - item(contentType = CONTENT_TYPE_HEADER) { + if (isSpaExpressiveEnabled) { + LazyCategory( + list = list, + entry = { index: Int -> + @Composable { + val appEntry = list[index] + val summary = getSummary(option, appEntry.record) ?: { "" } + remember(appEntry) { + AppListItemModel(appEntry.record, appEntry.label, summary) + } + .AppItem() + } + }, + key = { index: Int -> list[index].record.itemKey(option) }, + title = { index: Int -> getGroupTitle(option, list[index].record) }, + bottomPadding = bottomPadding, + state = rememberLazyListStateAndHideKeyboardWhenStartScroll(), + ) { header() } - - items(count = list.size, key = { list[it].record.itemKey(option) }) { - remember(list) { getGroupTitleIfFirst(option, list, it) } - ?.let { group -> CategoryTitle(title = group) } - - val appEntry = list[it] - val summary = getSummary(option, appEntry.record) ?: { "" } - remember(appEntry) { - AppListItemModel(appEntry.record, appEntry.label, summary) - }.AppItem() + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = rememberLazyListStateAndHideKeyboardWhenStartScroll(), + contentPadding = PaddingValues(bottom = bottomPadding), + ) { + item(contentType = CONTENT_TYPE_HEADER) { header() } + + items(count = list.size, key = { list[it].record.itemKey(option) }) { + remember(list) { getGroupTitleIfFirst(option, list, it) } + ?.let { group -> CategoryTitle(title = group) } + + val appEntry = list[it] + val summary = getSummary(option, appEntry.record) ?: { "" } + remember(appEntry) { + AppListItemModel(appEntry.record, appEntry.label, summary) + } + .AppItem() + } } } } } -private fun T.itemKey(option: Int) = - listOf(option, app.packageName, app.userId) +private fun T.itemKey(option: Int) = listOf(option, app.packageName, app.userId) /** Returns group title if this is the first item of the group. */ private fun AppListModel.getGroupTitleIfFirst( option: Int, list: List>, index: Int, -): String? = getGroupTitle(option, list[index].record)?.takeIf { - index == 0 || it != getGroupTitle(option, list[index - 1].record) -} +): String? = + getGroupTitle(option, list[index].record)?.takeIf { + index == 0 || it != getGroupTitle(option, list[index - 1].record) + } @Composable private fun rememberViewModel( @@ -183,16 +201,19 @@ private fun rememberViewModel( viewModel.searchQuery.Sync(state.searchQuery) LifecycleEffect(onStart = { viewModel.reloadApps() }) - val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply { - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(Intent.ACTION_PACKAGE_CHANGED) - addDataScheme("package") - } + val intentFilter = + IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply { + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_PACKAGE_CHANGED) + addDataScheme("package") + } for (userId in config.userIds) { DisposableBroadcastReceiverAsUser( intentFilter = intentFilter, userHandle = UserHandle.of(userId), - ) { viewModel.reloadApps() } + ) { + viewModel.reloadApps() + } } return viewModel } diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 81a2e6aeccc1c6edd4830b09852d034545fdba26..076f82acce0998cf83de8dd90fea9d21f722f2b7 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -73,16 +73,6 @@ flag { } } -flag { - name: "volume_panel_broadcast_fix" - namespace: "systemui" - description: "Make the volume panel's repository listen for the new ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED broadcast instead of ACTION_NOTIFICATION_POLICY_CHANGED" - bug: "347707024" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "volume_dialog_audio_sharing_fix" namespace: "cross_device_experiences" @@ -110,6 +100,14 @@ flag { bug: "323791114" } +flag { + name: "write_system_preference_permission_enabled" + is_fixed_read_only: true + namespace: "android_settings" + description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop" + bug: "375193223" +} + flag { name: "asha_profile_access_profile_enabled_true" namespace: "accessibility" @@ -166,3 +164,17 @@ flag { description: "Enable the ambient volume control in device details and hearing devices dialog." bug: "357878944" } + +flag { + name: "settings_preference_write_consent_enabled" + namespace: "android_settings" + description: "Enable the user consent prompt before writing sensitive preferences via service" + bug: "378552675" +} + +flag { + name: "hearing_devices_input_routing_control" + namespace: "accessibility" + description: "Enable the input routing control in device details and hearing devices dialog." + bug: "349255906" +} diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 79c379996d8bfddf4694279585b73a54872c5490..6f6a357a4a95d812e49697700a4bd058452daefb 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -591,6 +591,12 @@ "Eksterne toestel" "Gekoppelde toestel" "Hierdie foon" + + + + + + "Kan nie op hierdie toestel speel nie" "Gradeer rekening op om oor te skakel" "Kan nie aflaaie hier speel nie" diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 5cde078af81129be099998d22bf6ea4276a751a7..4d7731c5dd459d5d95c7e8afa0b6e2727244880b 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -591,6 +591,12 @@ "የውጭ መሣሪያ" "የተገናኘ መሣሪያ" "ይህ ስልክ" + + + + + + "በዚህ መሣሪያ ላይ ማጫወት አልተቻለም" "ለመቀየር መለያ ያልቁ" "ውርዶችን እዚህ ማጫወት አይቻልም" diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index f2d722ebe5cec9c9a63ac81142fc532b2161fbb1..8b8131342b7d31c74405b2ac8aa7fab0753f8f78 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -237,7 +237,7 @@ "تحديد الملف" "الملف الشخصي" "ملف العمل" - "الملف الخاص" + "المساخة الخاصة" "استنساخ" "خيارات المطورين" "تفعيل خيارات المطورين" @@ -591,6 +591,12 @@ "جهاز خارجي" "جهاز متّصل" "هذا الهاتف" + + + + + + "لا يمكن تشغيل الوسائط هنا" "يجب ترقية الحساب للتبديل" "المحتوى المنزَّل غير متوافق" diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 62d6f68e36115d6b4d8be4736ab933fcd6fd8e01..02324de0276f2d64ba3ab57e58bc9fda1f97aee5 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -591,6 +591,12 @@ "বাহ্যিক ডিভাইচ" "সংযোগ হৈ থকা ডিভাইচ" "এই ফ’নটো" + + + + + + "এই ডিভাইচটো প্লে\' কৰিব নোৱাৰি" "সলনি কৰিবলৈ একাউণ্ট আপগ্ৰে’ড কৰক" "ইয়াত ডাউনল’ডসমূহ প্লে’ কৰিব নোৱাৰি" diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 459cfaaae9c4714c472112a88e3c21e64a7d8efa..25e85d53cdceadb260470c8352a1357dfca652d7 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -591,6 +591,12 @@ "Xarici cihaz" "Qoşulmuş cihaz" "Bu telefon" + + + + + + "Bu cihazda oxutmaq olmur" "Keçirmək üçün hesabı güncəllə" "Burada endirmələri oxutmaq olmur" diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index f883c93fd15e6e0579a76641d6fdb98bb9318e4f..4214a401696540be1d2bec3a946de3628d2a69ba 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -591,6 +591,12 @@ "Spoljni uređaj" "Povezani uređaj" "Ovaj telefon" + + + + + + "Ne možete da pustite na ovom uređaju" "Nadogradite nalog radi prebacivanja" "Preuzimanja ne mogu da se puštaju ovde" diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index ca07c4def3a0ce9ea91a3d19eb054f1ea13f1ab4..3e645b3f1ae06cc125337a0af959668e2bb5230a 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -591,6 +591,12 @@ "Знешняя прылада" "Падключаная прылада" "Гэты тэлефон" + + + + + + "Не ўдаецца прайграць на гэтай прыладзе" "Для пераключэння перайдзіце на іншую версію ўліковага запісу" "Тут не ўдаецца прайграць спампоўкі" diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 7661a31d4dc23c439e6fe96773d07538b0b84dd3..8bdd17ebd861d708d024a105ef15380a89b41d7c 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -591,6 +591,12 @@ "Външно устройство" "Свързано устройство" "Този телефон" + + + + + + "Възпроизвеждането не е възможно на това устройство" "Надстройте профила, за да превключите" "Изтеглянията не могат да се възпроизвеждат тук" diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index ca98e0e510a55b9f2d2d82aab4666966795653d1..834eb1eef1ce5b3406293c10cbbdd7f27450ebd7 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -591,6 +591,12 @@ "এক্সটার্নাল ডিভাইস" "কানেক্ট থাকা ডিভাইস" "এই ফোনটি" + + + + + + "এই ডিভাইসে চালানো যাবে না" "পাল্টাতে অ্যাকাউন্ট আপগ্রেড করুন" "এতে ডাউনলোড করা কন্টেন্ট প্লে করা যাবে না" diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 72c2cbb9e73fbf2b35b90568e798dd214ccbcd70..75fe8181489dd5842d0ef4a9474e78762165d1d8 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -591,6 +591,12 @@ "Vanjski uređaj" "Povezani uređaj" "Ovaj telefon" + + + + + + "Nije moguće reproducirati na uređaju" "Nadogradite račun da promijenite" "Nije moguće reproducirati preuzimanja ovdje" diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 43653582576d61acaf575dc73cd2811d4c2192fe..2c41b1aee8ef9f72089f0ac2c0ef3f494ec51de0 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -591,6 +591,12 @@ "Dispositiu extern" "Dispositiu connectat" "Aquest telèfon" + + + + + + "No es pot reproduir en aquest dispositiu" "Actualitza el compte per canviar" "Les baixades no es poden reproduir aquí" diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 0a88338cf2645bb5dc98f7e7c0c21420ba1b2481..a4b491bfc1f038777696eefa748d4722be2e7c32 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -591,6 +591,12 @@ "Externí zařízení" "Připojené zařízení" "Tento telefon" + + + + + + "V zařízení nelze přehrávat" "Účet je třeba upgradovat" "Stažený obsah zde nelze přehrát" diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 983003bf1550f4f023511d0a1debc5ab164ff5d9..f7402732299b0ced95a128f84bb70f866ca46294 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -591,6 +591,12 @@ "Ekstern enhed" "Forbundet enhed" "Denne telefon" + + + + + + "Kan ikke afspilles på denne enhed" "Opgrader kontoen for at skifte" "Downloads kan ikke afspilles her" diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 75ebb57843b9a97649de08e2b34b2c278e422b4b..c8d8cb55de992dae3740770f7fe55897bb485210 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -591,6 +591,12 @@ "Externes Gerät" "Verbundenes Gerät" "Dieses Smartphone" + + + + + + "Wiedergabe auf diesem Gerät nicht möglich" "Zum Umstellen Kontoupgrade durchführen" "Downloads können hier nicht abgespielt werden" diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index c838f689528ec23d4f55da9faac1d38b589ece0b..c773372790b412058aa4ea7b59b73e9984965c7e 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -520,7 +520,7 @@ "Γρήγορη φόρτιση" "Ελέγχονται από το διαχειριστή" "Ελέγχεται από τη Ρύθμιση με περιορισμό" - "Απενεργοποιημένο" + "Απενεργοποιημένη" "Επιτρέπεται" "Δεν επιτρέπεται" "Εγκατ. άγνωστων εφ." @@ -591,6 +591,12 @@ "Εξωτερική συσκευή" "Συνδεδεμένη συσκευή" "Αυτό το τηλέφ." + + + + + + "Δεν είναι δυνατή η αναπαραγωγή σε αυτήν τη συσκευή" "Αναβαθμίστε τον λογαριασμό για εναλλαγή" "Δεν είναι δυνατή η αναπαραγωγή των λήψεων εδώ" diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index b4bf2055df0efa6b09d6edf87902147015d96e9e..8fe14350f3d90a8a8c17f84afea0703cc3e7270f 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -591,6 +591,12 @@ "External device" "Connected device" "This phone" + + + + + + "Can\'t play on this device" "Upgrade account to switch" "Can\'t play downloads here" diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index af392b83cfb122f5c1ddf952982c780f38f16daf..0d560ea7c8b9336d3668f31a0c1fd41b844007d2 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -591,6 +591,9 @@ "External Device" "Connected device" "This phone" + "S/PDIF" + "Analog" + "AUX" "Cant play on this device" "Upgrade account to switch" "Cant play downloads here" diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index b4bf2055df0efa6b09d6edf87902147015d96e9e..8fe14350f3d90a8a8c17f84afea0703cc3e7270f 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -591,6 +591,12 @@ "External device" "Connected device" "This phone" + + + + + + "Can\'t play on this device" "Upgrade account to switch" "Can\'t play downloads here" diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index b4bf2055df0efa6b09d6edf87902147015d96e9e..8fe14350f3d90a8a8c17f84afea0703cc3e7270f 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -591,6 +591,12 @@ "External device" "Connected device" "This phone" + + + + + + "Can\'t play on this device" "Upgrade account to switch" "Can\'t play downloads here" diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 3c1a27822a3cedcb375580d1e5d05e549bed681b..99dd4d9a56b97dd4b18fc55adad11f0445fafe1c 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo conectado" "Este teléfono" + + + + + + "No se puede reproducir en este dispositivo" "Actualiza la cuenta para cambiar" "No se pueden reproducir las descargas aquí" diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index bafa654a5284479d14e81e046ec2732888983bbe..cbbadd44d357c7162a34bdb552e4ee76d6fef21e 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo conectado" "Este teléfono" + + + + + + "No se puede reproducir en este dispositivo" "Actualiza la cuenta para cambiar" "No se pueden reproducir descargas aquí" diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 5f5a38c1464bbbd9aa6610576bca8002a2ee267f..3c337933fbfbeca4aaa90153481c47a64f8098fc 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -591,6 +591,12 @@ "Väline seade" "Ühendatud seade" "See telefon" + + + + + + "Selles seadmes ei saa esitada" "Lülitamiseks täiendage kontot" "Siin ei saa allalaaditud faile esitada" diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 48e4f62e5865fa88561740ad03f74103cf0e41a5..c9947485e5f253d5772acb9bbef171660b10ccd3 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -591,6 +591,12 @@ "Kanpoko gailua" "Konektatutako gailua" "Telefono hau" + + + + + + "Ezin da erreproduzitu gailu honetan" "Aldatzeko, bertsio-berritu kontua" "Deskargak ezin dira hemen erreproduzitu" diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 1aeca86aed6c8644c966f4786b3b946c22b86f8e..75b8051098240603edbec8b927ff0be649fb088b 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -591,6 +591,12 @@ "دستگاه خارجی" "دستگاه متصل" "این تلفن" + + + + + + "نمی‌توان در این دستگاه پخش کرد" "برای تغییر، حساب را ارتقا دهید" "نمی‌توان بارگیری‌ها را در اینجا پخش کرد" diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 53724a3b7d8617e2d6915d763376b40d6a7a023a..9cc33b013979cb9a5e2e53dc35488f1b4400649f 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -591,6 +591,12 @@ "Ulkoinen laite" "Yhdistetty laite" "Tämä puhelin" + + + + + + "Ei voi toistaa tällä laitteella" "Vaihda päivittämällä tili" "Latauksia ei voi toistaa täällä" diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 1e4bbc43ed084bf3a7d9bb03c741db9357c57c02..28692d0afa55e2067f7d548d04381160bc202367 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -512,7 +512,7 @@ "En recharge sans fil" "Recharge en cours…" "N\'est pas en charge" - "Connecté, mais ne se recharge pas" + "Appareil connecté, mais pas en cours de recharge" "Chargée" "Complètement rechargée" "Recharge en pause" @@ -591,6 +591,12 @@ "Appareil externe" "Appareil connecté" "Ce téléphone" + + + + + + "Impossible de faire jouer le contenu sur cet appareil" "Mettez à jour le compte pour passer à la version payante" "Lecture des téléchargements impossible ici" diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index f4afc8701609c0a0b330c093c95fc1970c27d4d7..4e7abc938ccf5336c88277ad76d9c5afd102ad6c 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -464,7 +464,7 @@ "Désactiver la cache de transcodage" "Services en cours d\'exécution" "Afficher et contrôler les services en cours d\'exécution" - "Mise en œuvre WebView" + "Implémentation WebView" "Définir la mise en œuvre WebView" "Ce choix n\'est plus valide. Réessayez." "Mode de couleur des images" @@ -591,6 +591,12 @@ "Appareil externe" "Appareil connecté" "Ce téléphone" + + + + + + "Impossible de lire du contenu sur cet appareil" "Mettez à niveau le compte pour changer" "Impossible de lire les téléchargements ici" diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index be1fac816f7d92b73ad26227430be8973819b212..a4d3a0a4f751165a903ba77553b644494d804c97 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo conectado" "Este teléfono" + + + + + + "Non se pode reproducir contido neste dispositivo" "Cambia a conta a un plan superior para facer a modificación" "Non se poden reproducir as descargas neste dispositivo" diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 05ec0e15da65eb5a04ad10e31a71c9691906ceb5..7a26463bc9bd3a7745f79e8eb7bfed38e86ebb6a 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -591,6 +591,12 @@ "બહારનું ડિવાઇસ" "કનેક્ટ કરેલું ડિવાઇસ" "આ ફોન" + + + + + + "આ ડિવાઇસ પર ચલાવી શકતા નથી" "સ્વિચ કરવા માટે એકાઉન્ટ અપગ્રેડ કરો" "ડાઉનલોડ કરેલું કન્ટેન્ટ અહીં ચલાવી શકતા નથી" diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 352dba79565d0286998d433f7f42a5536e0e4f6f..83cb3989e909fb42afdb60fb109cfc80d5e01c38 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -591,6 +591,12 @@ "बाहरी डिवाइस" "कनेक्ट किया गया डिवाइस" "यह फ़ोन" + + + + + + "इस डिवाइस पर मीडिया नहीं चलाया जा सकता" "प्रीमियम खाते में स्विच करने के लिए, अपना खाता अपग्रेड करें" "डाउनलोड किए गए वीडियो यहां नहीं चलाए जा सकते" diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 2cf02fc2ff39e63514f71be80a46a57f41ab72ef..e78884d65538f38979d713ee1def030646c01578 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -591,6 +591,12 @@ "Vanjski uređaj" "Povezani uređaj" "Ovaj telefon" + + + + + + "Ne može se reproducirati ovdje" "Nadogradite i prebacite se" "Ne može se tu reproducirati" diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 86a959a97cd098ce77f92c7e645ed3978b500de8..ca18ac2e191fa820b3bfa21a62cc6cb67e95f1a5 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -591,6 +591,12 @@ "Külső eszköz" "Csatlakoztatott eszköz" "Ez a telefon" + + + + + + "Nem játszható le ezen az eszközön" "A váltáshoz frissítse fiókját" "Itt nem játszhatók le a letöltések" diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 4c29740b552af4e99ad1e0ff060cb248c8eb6eb0..0749d6042b6baf6723301adc0fd7db4d80ad50e4 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -591,6 +591,12 @@ "Արտաքին սարք" "Միացված սարք" "Այս հեռախոսը" + + + + + + "Հնարավոր չէ նվագարկել այս սարքում" "Փոխելու համար անցեք հաշվի պրեմիում տարբերակին" "Ներբեռնումները չեն նվագարկվում այստեղ" diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index bfd8f397be601e451a379e3203628c1100b595fa..7d652c9db6835ef883f952b5dbf405598b3f70b9 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -591,6 +591,12 @@ "Perangkat Eksternal" "Perangkat yang terhubung" "Ponsel ini" + + + + + + "Tidak dapat memutar di perangkat ini" "Upgrade akun untuk beralih" "Tidak dapat memutar hasil download di sini" diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index d0184e112fc0185bf24f122eb0382f09cb36a5f7..67f4fe1b9562a32b319bb69a411814a80c16d07c 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -591,6 +591,12 @@ "Ytra tæki" "Tengt tæki" "Þessi sími" + + + + + + "Ekki er hægt að spila í þessu tæki" "Uppfærðu reikninginn til að skipta" "Ekki er hægt að spila niðurhal hér" diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 773aa2659171a78398bd545049fd17b18bd6d4a2..9f7f41b8b16f5401751cf8d9958c620bdd6d7dc2 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -591,6 +591,12 @@ "Dispositivo esterno" "Dispositivo connesso" "Questo smartphone" + + + + + + "Impossibile riprodurre su questo dispositivo" "Esegui l\'upgrade dell\'account per cambiare" "Qui non è possibile riprodurre i download" diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 5f63ed371f424d81cd648040a87d722147336591..056aae170610e7ab6220ccf49a81665f24713c82 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -591,6 +591,12 @@ "מכשיר חיצוני" "המכשיר המחובר" "הטלפון הזה" + + + + + + "לא ניתן להפעיל במכשיר" "יש לשדרג חשבון כדי לעבור" "לא ניתן להפעיל הורדות" diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index ae3f1970d20624d52e7b8323f91aa23f5a86ce35..833d1dcf4aefa0369aed61b4d39275899500f4be 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -591,6 +591,12 @@ "外部デバイス" "接続済みのデバイス" "このデバイス" + + + + + + "このデバイスでは再生できません" "アカウントを更新して切り替えてください" "再生不可: ダウンロードしたコンテンツ" diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index fe9dbb52cdd06a577d710226eed70ba4cbf1f2b1..acad174698fb1593ce838a12df3e8d3e655be469 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -591,6 +591,12 @@ "გარე მოწყობილობა" "დაკავშირებული მოწყობილობა" "ეს ტელეფონი" + + + + + + "ამ მოწყობილობაზე დაკვრა შეუძლებელია" "გადასართავად განაახლეთ ანგარიში" "შეუძლებელია აქ ჩამოტვირ. თამაში" diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 1162e8f8a00f1130372d409ab0a3aed791e346b9..e2ce5b0e8ef32090825c12cf04d2bbc9b0099740 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -591,6 +591,12 @@ "Сыртқы құрылғы" "Жалғанған құрылғы" "Осы телефон" + + + + + + "Осы құрылғыда ойнату мүмкін емес." "Ауысу үшін аккаунтты жаңартыңыз." "Жүктеп алынғандарды осы жерде ойнату мүмкін емес." diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index e6935d0d034eb238ba36bfa82a0305b659dfc882..c06aaeeb4bda182dd6dd0119d8a18eaf0f81a9b3 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -591,6 +591,12 @@ "ឧបករណ៍ខាងក្រៅ" "​ឧបករណ៍ដែលបាន​ភ្ជាប់" "ទូរសព្ទនេះ" + + + + + + "មិនអាចចាក់នៅលើ​ឧបករណ៍នេះបានទេ" "ដំឡើងកម្រិតគណនី ដើម្បីប្ដូរ" "មិនអាចចាក់ខ្លឹមសារដែលបានទាញយកនៅទីនេះបានទេ" diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index f34a43cfdd195fff91f8b360c26eea0612fb1aff..2c25501e60a79fb4fe95de7111ee4dfa2fb4a4da 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -591,6 +591,12 @@ "ಬಾಹ್ಯ ಸಾಧನ" "ಕನೆಕ್ಟ್ ಮಾಡಿರುವ ಸಾಧನ" "ಈ ಫೋನ್" + + + + + + "ಈ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" "ಬದಲಾಯಿಸಲು ಖಾತೆಯನ್ನು ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ" "ಇಲ್ಲಿ ಡೌನ್‌ಲೋಡ್‌ಗಳನ್ನು ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 1e51ae640301bd5e445b1a5b6bdded3368c58355..f7dc6ae8603dae475b67c9c1a0788830b1509e04 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -591,6 +591,12 @@ "외부 기기" "연결된 기기" "이 휴대전화" + + + + + + "이 기기에서 재생할 수 없음" "계정을 업그레이드하여 전환하기" "여기서 다운로드한 콘텐츠를 재생할 수 없습니다." diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 785bf43bb4d139579dca1ab945e468afcb200c7b..6105db0bf071e97c9abc34429a0c9392db8a56d7 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -591,6 +591,12 @@ "Тышкы түзмөк" "Туташкан түзмөк" "Ушул телефон" + + + + + + "Бул түзмөктө ойнотууга болбойт" "Премиум аккаунтка которулуу керек" "Жүктөлүп алынгандар ойнотулбайт" diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 740586e91f562eeffb52d8d1f2550c260f32901e..df3689e72249d39aa1a0dfbaa4c9c6f0968551d4 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -591,6 +591,12 @@ "ອຸປະກອນພາຍນອກ" "ອຸປະກອນທີ່ເຊື່ອມຕໍ່" "ໂທລະສັບນີ້" + + + + + + "ຫຼິ້ນຢູ່ອຸປະກອນນີ້ບໍ່ໄດ້" "ອັບເກຣດບັນຊີເພື່ອສະຫຼັບ" "ບໍ່ສາມາດຫຼິ້ນເນື້ອຫາທີ່ດາວໂຫຼດຢູ່ນີ້ໄດ້" diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 0c235b8a6f35ff867a2b7fa40b205853aa8a1631..4d9858733f863e8f538e483fca5248b4b0b93918 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -591,6 +591,12 @@ "Išorinis įrenginys" "Prijungtas įrenginys" "Šis telefonas" + + + + + + "Negalima leisti šiame įrenginyje" "Jei norite perjungti, naujovinkite paskyrą" "Čia negalima paleisti atsisiuntimų" diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index daaebc880ec9e021109048fbb47a65d3a404ff71..5829826734410e826a09b0a7f49cbf2f9bf7e4f4 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -591,6 +591,12 @@ "Ārēja ierīce" "Pievienotā ierīce" "Šis tālrunis" + + + + + + "Nevar atskaņot šajā ierīcē." "Lai pārslēgtu, jauniniet kontu" "Šeit nevar atskaņot lejupielādes" diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index b9d970d8388f9515c63069a9fb08938f0f47a5c9..5d5d480ecd689ec8a493f176865f1931fa9fe202 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -591,6 +591,12 @@ "Надворешен уред" "Поврзан уред" "Овој телефон" + + + + + + "Не може да се пушти на уредов" "Надградете ја сметката за да се префрлите" "Не може да се пуштаат преземања тука" diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 7dd2073642f3902c1b642db9866281f599963508..71406232cb64e0334f35254d9f57ae19178e2d06 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -591,6 +591,12 @@ "ബാഹ്യ ഉപകരണം" "കണക്‌റ്റ് ചെയ്‌ത ഉപകരണം" "ഈ ഫോൺ" + + + + + + "ഈ ഉപകരണത്തിൽ പ്ലേ ചെയ്യാൻ കഴിയില്ല" "അക്കൗണ്ട് മാറാൻ അപ്‌ഗ്രേഡ് ചെയ്യുക" "ഡൗൺലോഡുകൾ പ്ലേ ചെയ്യാനാകില്ല" diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 4f247ab410a55c36b4f68137507b63d53f712c7a..c9e0178ce54fa174aca534326ffff876ef198d43 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -591,6 +591,12 @@ "Гадаад төхөөрөмж" "Холбогдсон төхөөрөмж" "Энэ утас" + + + + + + "Энэ төхөөрөмжид тоглуулах боломжгүй" "Сэлгэхийн тулд бүртгэлийг сайжруулна уу" "Татаж авсан файлыг энд тоглуулах боломжгүй" diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index be9464c30241dc31dbcb00240cfe59db0f24fb04..a6596cdf7d0c844762cd317cff5eacabfb8d152a 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -591,6 +591,12 @@ "बाह्य डिव्हाइस" "कनेक्ट केलेले डिव्हाइस" "हा फोन" + + + + + + "या डिव्हाइसवर प्ले करू शकत नाही" "स्विच करण्यासाठी खाते अपग्रेड करा" "येथे डाउनलोड प्ले केले जाऊ शकत नाहीत" diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 8f67d339fba0e8e05228cd9a24fe9d471242e18f..ebde3319a806a675706c46a5429248fab9c10a2f 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -591,6 +591,12 @@ "Peranti Luar" "Peranti yang disambungkan" "Telefon ini" + + + + + + "Tidak dapat dimainkan pada peranti ini" "Tingkatkan akaun untuk beralih" "Tidak dapat memainkan muat turun di sini" diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 2ced73bba26aea1720cd1cb3be83f6532d2051d1..1131007fe1bd2abc062d7d5c8a6fa0863970c39b 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -591,6 +591,12 @@ "ပြင်ပစက်" "ချိတ်ဆက်ကိရိယာ" "ဤဖုန်း" + + + + + + "ဤစက်ပစ္စည်းတွင် ဖွင့်၍မရပါ" "ပြောင်းရန် အကောင့်အဆင့်ကိုမြှင့်ပါ" "ဤနေရာတွင် ဒေါင်းလုဒ်များ ဖွင့်မရပါ" diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index e16fea25271ef8d68bdc807c7eccc75bf574a756..1a71ec07457ee918b1f6e0b5928ce33b2124f7ca 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -591,6 +591,12 @@ "Ekstern enhet" "Tilkoblet enhet" "Denne telefonen" + + + + + + "Kan ikke spille på denne enheten" "Oppgrader kontoen for å bytte" "Kan ikke spille av nedlastinger her" diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index b26376179c6231d87c57cf2d8f69ccc3efe82ff7..dea9fcce87cf830d60d86f8785a2af04ee5c9690 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -184,7 +184,7 @@ "Android OS" "हटाइएका एपहरू" "एपहरू र प्रयोगकर्ताहरू हटाइयो।" - "प्रणालीसम्बन्धी अद्यावधिकहरू" + "प्रणालीसम्बन्धी अपडेटहरू" "USB टेदरिङ" "पोर्टेबल हटस्पट" "ब्लुटुथ टेदर गर्दै" @@ -591,6 +591,12 @@ "बाह्य डिभाइस" "कनेक्ट गरिएको डिभाइस" "यो फोन" + + + + + + "यो डिभाइसमा मिडिया प्ले गर्न मिल्दैन" "आफूले प्रयोग गर्न चाहेको खाता अपग्रेड गर्नुहोस्" "डाउनलोड गरिएका सामग्री यसमा प्ले गर्न मिल्दैन" diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 75d561762967791c05c4bcb6ae94dbe6d8f24e50..e3be50736a88691be83d9a13e46507dfd39471a9 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -591,6 +591,12 @@ "Extern apparaat" "Verbonden apparaat" "Deze telefoon" + + + + + + "Kan niet afspelen op dit apparaat" "Upgrade het account om te schakelen" "Kan hier geen downloads afspelen" diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index ae1df210bce3e1c87f94b38052c01c1928bc3805..ea6fdba27506e3e5465d6d8cc00a8ce81feff805 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -591,6 +591,12 @@ "ଏକ୍ସଟର୍ନଲ ଡିଭାଇସ" "କନେକ୍ଟ କରାଯାଇଥିବା ଡିଭାଇସ" "ଏହି ଫୋନ୍" + + + + + + "ଏହି ଡିଭାଇସରେ ପ୍ଲେ କରାଯାଇପାରିବ ନାହିଁ" "ସ୍ୱିଚ କରିବା ପାଇଁ ଆକାଉଣ୍ଟକୁ ଅପଗ୍ରେଡ କରନ୍ତୁ" "ଏଠାରେ ଡାଉନଲୋଡଗୁଡ଼ିକୁ ପ୍ଲେ କରାଯାଇପାରିବ ନାହିଁ" diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index c1ef613f046e4fc795f09349fb694cba38baa0d7..853405167e01feda75fdbd85db614d89722735d4 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -591,6 +591,12 @@ "ਬਾਹਰੀ ਡੀਵਾਈਸ" "ਕਨੈਕਟ ਕੀਤਾ ਡੀਵਾਈਸ" "ਇਹ ਫ਼ੋਨ" + + + + + + "ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਨਹੀਂ ਚਲਾਇਆ ਜਾ ਸਕਦਾ" "ਸਵਿੱਚ ਕਰਨ ਲਈ ਖਾਤੇ ਨੂੰ ਅੱਪਗ੍ਰੇਡ ਕਰੋ" "ਡਾਊਨਲੋਡਾਂ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਚਲਾਇਆ ਜਾ ਸਕਦਾ" diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 0a8ea16b0279dcbbb8df481ba84e81422bd304a7..a9d4dcbf577aa6de0c05ac4bf373aff0d4987f0c 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -520,7 +520,7 @@ "Szybkie ładowanie" "Kontrolowane przez administratora" "Obowiązują ustawienia z ograniczonym dostępem" - "Wyłączone" + "Wyłączona" "Dozwolone" "Niedozwolone" "Instalowanie nieznanych aplikacji" @@ -591,6 +591,12 @@ "Urządzenie zewnętrzne" "Połączone urządzenie" "Ten telefon" + + + + + + "Nie można odtworzyć na tym urządzeniu" "Aby przełączyć, potrzebujesz konta premium" "Tutaj nie można odtworzyć pobranych plików" diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index c39648817af92b7448801cf9c8d4c692a1e0016a..eac646ff4c105fd892fd1fa7d2c6f921a3d89476 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo conectado" "Neste telefone" + + + + + + "Não é possível reproduzir neste dispositivo" "Faça upgrade da conta para trocar" "Não é possível abrir os downloads aqui" diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index be91222c38f266937e2aca5b7ac7f043677791ef..d8d172d25192c371a9f935bfc9cd73b987252851 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo associado" "Este telemóvel" + + + + + + "Não é possível reproduzir neste dispositivo" "Atualize a conta para mudar" "Não é possível reproduzir as transferências aqui" diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index c39648817af92b7448801cf9c8d4c692a1e0016a..eac646ff4c105fd892fd1fa7d2c6f921a3d89476 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -591,6 +591,12 @@ "Dispositivo externo" "Dispositivo conectado" "Neste telefone" + + + + + + "Não é possível reproduzir neste dispositivo" "Faça upgrade da conta para trocar" "Não é possível abrir os downloads aqui" diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 4a0023fd63873404907a8c54fe57901e0df9988b..d507b8526c8d102dffed888a017e22c9599b6677 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -196,7 +196,7 @@ "Unele valori prestabilite sunt configurate" "Nu este configurată nicio valoare prestabilită" "Setări redare vocală a textului" - "Setări pentru redarea vocală a textului" + "Redare vocală a textului" "Ritmul vorbirii" "Viteza cu care este vorbit textul" "Înălțime" @@ -591,6 +591,12 @@ "Dispozitiv extern" "Dispozitiv conectat" "Acest telefon" + + + + + + "Nu se poate reda pe acest dispozitiv" "Fă upgrade contului pentru a comuta" "Aici nu se pot reda descărcări" diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 91648df5f96fef870802fcb1e4587300d9448589..8d07c57fec56dcb1df666203da4176e2b57955b5 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -283,7 +283,7 @@ "Во время зарядки экран будет всегда включен" "Включить snoop-логи Bluetooth HCI" "Сохранять все пакеты Bluetooth (перезапустите Bluetooth после изменения этой настройки)" - "Заводская разблокировка" + "Разблокировка загрузчика" "Разрешить разблокировку загрузчика ОС" "Разрешить заводскую разблокировку?" "ВНИМАНИЕ! Функции защиты не будут работать на устройстве, пока включен этот параметр." @@ -591,6 +591,12 @@ "Внешнее устройство" "Подключенное устройство" "Этот смартфон" + + + + + + "Невозможно воспроизвести на этом устройстве." "Для переключения требуется премиум-аккаунт" "Не удается воспроизвести скачанные файлы" diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index a9004d0ebedc70a8a6bae12564262482bd3e50c8..6694bc552e11130645f978f9bf417f6a34ce433b 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -591,6 +591,12 @@ "බාහිර උපාංගය" "සම්බන්ධ කළ උපාංගය" "මෙම දුරකථනය" + + + + + + "මෙම උපාංගය මත ධාවනය කළ නොහැක" "මාරු වීමට ගිණුම උත්ශ්‍රේණි කරන්න" "මෙහි බාගැනීම් වාදනය කළ නොහැක" diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 8fc99cfd7416f560efe5dde957cb5a88bd5ed286..fa525a20ca66c9a27935eb4d85082eda7c314b09 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -591,6 +591,12 @@ "Externé zariadenie" "Pripojené zariadenie" "Tento telefón" + + + + + + "V tomto zariadení sa nedá prehrávať obsah" "Inovujte účet a prejdite naň" "Tu sa nedajú prehrať stiahnuté súbory" diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 0945cc042dc5d56059ac167c648fb9d95622b0a4..9b426aa27e19688bcd656c7a395b554882458d0d 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -591,6 +591,12 @@ "Zunanja naprava" "Povezana naprava" "Ta telefon" + + + + + + "Ni mogoče predvajati v tej napravi." "Za preklop je potrebna nadgradnja računa" "Prenosov tu ni mogoče predvajati" diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 0b39551065572d1be0f6d16c16854f75b5d516b8..73cc518dac0aa6ee392c31a586be82cf9c62ae01 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -304,12 +304,12 @@ "Zgjidh versionin AVRCP të Bluetooth-it" "Versioni MAP i Bluetooth-it" "Zgjidh versionin MAP të Bluetooth-it" - "Kodeku Bluetooth Audio" + "Kodeku i audios me Bluetooth" "Aktivizo kodekun e audios me Bluetooth\nZgjedhja" "Shpejtësia e shembullit të Bluetooth Audio" "Aktivizo kodekun e audios me Bluetooth\nZgjedhja: Shpejtësia e shembullit" "Çaktivizimi do të thotë se nuk mbështetet nga telefoni ose kufjet" - "Bite për shembull Bluetooth Audio" + "Bite të audios me Bluetooth për shembull" "Aktivizo kodekun e audios me Bluetooth\nZgjedhja: Bite për shembull" "Modaliteti i kanalit të audios me Bluetooth" "Aktivizo kodekun e audios me Bluetooth\nZgjedhja: Modaliteti i kanalit" @@ -591,6 +591,12 @@ "Pajisja e jashtme" "Pajisja e lidhur" "Ky telefon" + + + + + + "Nuk mund të luhet në këtë pajisje" "Përmirëso llogarinë për të ndryshuar" "Shkarkimet nuk mund të luhen këtu" diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index d351a12bac21cc1a65c3f15fa88c2f11ad4bafd3..d8003d75bd4e176bc997bf68e09837402efbed04 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -591,6 +591,12 @@ "Спољни уређај" "Повезани уређај" "Овај телефон" + + + + + + "Не можете да пустите на овом уређају" "Надоградите налог ради пребацивања" "Преузимања не могу да се пуштају овде" diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 1d7f62ac6c9397b75942e28b55584edf7a031885..f9bd295253a6227ccc2a124132043d1f75359b33 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -235,7 +235,7 @@ "400 %" "Välj profil" - "Privat" + "Personlig" "Jobb" "Privat" "Klon" @@ -591,6 +591,12 @@ "Extern enhet" "Ansluten enhet" "Den här telefonen" + + + + + + "Kan inte spelas på denna enhet" "Uppgradera kontot för att byta" "Det går inte att spela upp nedladdningar här" diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 599113ce6a07203c834e4280f545d8cbe67f79a6..b86be31ec352b531b9c97a423628c1392a5b13d7 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -591,6 +591,12 @@ "Kifaa cha Nje" "Kifaa kilichounganishwa" "Simu hii" + + + + + + "Huwezi kucheza maudhui kwenye kifaa hiki" "Pata toleo jipya la akaunti ili ubadilishe" "Imeshindwa kucheza maudhui yaliyopakuliwa hapa" diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 98412c1f41f47480678e2b66810a6ce9771bdf06..1fd78d3842eb6c5a1f66d17168fdec9280d445b9 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -591,6 +591,12 @@ "வெளிப்புறச் சாதனம்" "இணைக்கப்பட்டுள்ள சாதனம்" "இந்த மொபைல்" + + + + + + "இந்தச் சாதனத்தில் பிளே செய்ய முடியவில்லை" "மாற்ற, கணக்கை மேம்படுத்துங்கள்" "பதிவிறக்கங்களை இங்கே பிளே செய்ய முடியாது" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 3770bb776627fc21462bcd8d53018aaa371759a3..fc45d2c8e1495281dcab410818915dbf069eab86 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -591,6 +591,12 @@ "ఎక్స్‌టర్నల్ పరికరం" "కనెక్ట్ చేసిన పరికరం" "ఈ ఫోన్" + + + + + + "ఈ పరికరంలో ప్లే చేయడం సాధ్యపడదు" "మారడానికి ఖాతాను అప్‌గ్రేడ్ చేయండి" "ఇక్కడ డౌన్‌లోడ్‌లను ప్లే చేయడం సాధ్యపడదు" diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 060b7b267624ef983a201c39e2476111b86aeac6..d525bc538ef83548dd06467303bec81b2c4087c1 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -591,6 +591,12 @@ "อุปกรณ์ภายนอก" "อุปกรณ์ที่เชื่อมต่อ" "โทรศัพท์เครื่องนี้" + + + + + + "เล่นในอุปกรณ์นี้ไม่ได้" "อัปเกรดบัญชีเพื่อเปลี่ยน" "เล่นเนื้อหาที่ดาวน์โหลดที่นี่ไม่ได้" diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index e869e7f3bbb49020f41a3b394c0ea08027d2dbe0..1df74736d879212b75a316edbf9e394a0d287f3d 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -591,6 +591,12 @@ "External na Device" "Nakakonektang device" "Ang teleponong ito" + + + + + + "Hindi ma-play sa device na ito" "I-upgrade ang account para lumipat" "Hindi mape-play ang mga download dito" diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 188f5aa2ce8e96ec5cef71312772e0e5e0b89a7a..d62269605e846dac53a78a9d2c70c57bb40f80a5 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -591,6 +591,12 @@ "Harici Cihaz" "Bağlı cihaz" "Bu telefon" + + + + + + "Bu cihazda oynatılamıyor" "Geçiş yapmak için hesabı yükseltin" "İndirilenler burada oynatılamaz" diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index b82db3a8a85fdb0a1c6e22ff6ee0f777e9e4e938..eb10cb209c2093fe823db68dfd4c9fa266aaaff6 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -591,6 +591,12 @@ "Зовнішній пристрій" "Підключений пристрій" "Цей телефон" + + + + + + "Не можна відтворювати тут" "Потрібний платний обліковий запис" "Завантаження не відтворюватимуться" diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 43f03a20e0fc733adac5ccfa04f47553a2f78915..8b2eb3fb018908e71b91425f7cbea5783e2e2a88 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -591,6 +591,12 @@ "بیرونی آلہ" "منسلک آلہ" "یہ فون" + + + + + + "اس آلے پر چلایا نہیں جا سکتا" "سوئچ کرنے کے لیے اکاؤنٹ اپ گریڈ کریں" "ڈاؤن لوڈز کو یہاں چلایا نہیں جا سکتا" diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 685d6e63b0823ee76e652566e6a5041fb1dcd635..62a6303fd45b76df681e7a62f7d279d5ac48f59f 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -591,6 +591,12 @@ "Tashqi qurilma" "Ulangan qurilma" "Shu telefon" + + + + + + "Bu qurilmada ijro etilmaydi" "Oʻtish uchun hisobingizni yangilang" "Yuklab olingan fayllar ijro etilmaydi" diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 7e98afddbe105d40f233d6c6b18477acd5e5fc79..1b90818fbaf9175290d592e60a20caeb053270a2 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -591,6 +591,12 @@ "Thiết bị bên ngoài" "Thiết bị đã kết nối" "Điện thoại này" + + + + + + "Không phát được trên thiết bị này" "Nâng cấp tài khoản để chuyển đổi" "Không thể phát các tệp đã tải xuống tại đây" diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 0f3373e1142f8e57fe6c0ac0fa003ff301013575..94915650223fb2ac1f64b15b6a7685b071588a6a 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -591,6 +591,12 @@ "外部设备" "连接的设备" "这部手机" + + + + + + "无法在此设备上播放" "升级账号后才能切换" "无法在此设备上播放下载的内容" diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index c8666ff88a25623e54e1ea0523b932916ef26803..aa3ac06298c5da576fb4a8bc8065da81498eb0c6 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -591,6 +591,12 @@ "外部裝置" "已連接的裝置" "這部手機" + + + + + + "無法在此裝置上播放" "請升級要切換的帳戶" "無法在此播放下載內容" diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 08bf732652b94f7808a54a6f27e708657f95419a..b5eb87da6de3acefa1a7551eaf0c3bb96c731537 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -591,6 +591,12 @@ "外部裝置" "已連結的裝置" "這支手機" + + + + + + "無法在這部裝置上播放" "請升級要切換的帳戶" "這裡無法播放下載內容" diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index ad4f04555d05509c65aa0044d5e0c8d9aba64374..57e0b8d8afeb25076fe4d1d23b0016f28f777c47 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -591,6 +591,12 @@ "Idivayisi Yangaphandle" "Idivayisi exhunyiwe" "Le foni" + + + + + + "Ayikwazi ukudlala kule divayisi" "Thuthukisa i-akhawunti ukuze ushintshe" "Awukwazi ukudlala okudawunilodiwe lapha" diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index bc144d60d238df4d4465b14650f10155ee8650d0..f03014ca95e286be867b365fd2960bfb17b62d6a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -821,8 +821,10 @@ Linux development environment - - Run Linux terminal on Android + + (Experimental) Run Linux terminal on Android + + If you disable, Linux terminal data will be cleared HDCP checking diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index a87b8153b85827c6b55db38d0a2fd3d425d33c54..216574a5fff951d53e6c77d4c88895ee7af24b87 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1,6 +1,7 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER; +import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix; import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED; import android.annotation.SuppressLint; @@ -651,6 +652,13 @@ public class BluetoothUtils { context.getContentResolver())); } + /** Returns if the le audio sharing hysteresis mode fix is available. */ + @WorkerThread + public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) { + return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing()) + || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver())); + } + /** Returns if the le audio sharing is enabled. */ public static boolean isAudioSharingEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -733,13 +741,15 @@ public class BluetoothUtils { @WorkerThread public static boolean hasConnectedBroadcastSourceForBtDevice( @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) { - if (Flags.audioSharingHysteresisModeFix()) { + if (localBtManager == null) { + Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null"); + return false; + } + if (isAudioSharingHysteresisModeFixAvailable(localBtManager.getContext())) { return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager); } LocalBluetoothLeBroadcastAssistant assistant = - localBtManager == null - ? null - : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); + localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); if (device == null || assistant == null) { Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null"); return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 6a9d5687370e11b3ff135e4f4cc1318e5c613ed0..dc52b4dafd9bd430941dc071d1a3639c19b90941 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -18,6 +18,8 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import static com.android.settingslib.Utils.isAudioModeOngoingCall; + import static java.util.stream.Collectors.toList; import android.annotation.CallbackExecutor; @@ -54,7 +56,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.settingslib.R; -import com.android.settingslib.flags.Flags; import com.google.common.collect.ImmutableList; @@ -303,6 +304,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + ", sourceId = " + sourceId); } + updateFallbackActiveDeviceIfNeeded(); } @Override @@ -390,9 +392,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + ", state = " + state); } - if (BluetoothUtils.isConnected(state)) { - updateFallbackActiveDeviceIfNeeded(); - } } }; @@ -1130,18 +1129,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile."); return; } - if (mServiceBroadcast == null) { - Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null"); - return; - } - List sources = mServiceBroadcast.getAllBroadcastMetadata(); - if (sources.stream() - .noneMatch(source -> mServiceBroadcast.isPlaying(source.getBroadcastId()))) { - Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no broadcast ongoing"); - return; - } - if (mServiceBroadcastAssistant == null) { - Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null"); + if (isAudioModeOngoingCall(mContext)) { + Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to ongoing call"); return; } Map> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast(); @@ -1152,7 +1141,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( mContext.getContentResolver()); - if (Flags.audioSharingHysteresisModeFix()) { + if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)) { int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId(); if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) { @@ -1193,7 +1182,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { @NonNull private Map> getDeviceGroupsInBroadcast() { - boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix(); + boolean hysteresisModeFixEnabled = + BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext); List connectedDevices = mServiceBroadcastAssistant.getConnectedDevices(); return connectedDevices.stream() .filter( diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt index 91a99aed6db5dbe384b741a09a3998eb5f2fd22d..a0a6d2698d8c69cdd6eca49b85cb4d935aa7af14 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt @@ -35,11 +35,7 @@ val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow sink: BluetoothDevice, sourceId: Int, state: BluetoothLeBroadcastReceiveState - ) { - if (BluetoothUtils.isConnected(state)) { - launch { send(Unit) } - } - } + ) {} override fun onSourceRemoved(sink: BluetoothDevice, sourceId: Int, reason: Int) { launch { send(Unit) } @@ -55,7 +51,9 @@ val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow override fun onSourceFound(source: BluetoothLeBroadcastMetadata) {} - override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {} + override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) { + launch { send(Unit) } + } override fun onSourceAddFailed( sink: BluetoothDevice, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt index edd49c5a8fb793308098289a9382688f8bc778b4..0209eb8c3fbf8cd8588a269ee5b77f2c04ef38b2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt @@ -21,6 +21,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.os.DeadObjectException import android.os.IBinder import android.os.IInterface import android.os.RemoteException @@ -52,6 +53,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.filterIsInstance @@ -304,6 +306,14 @@ class DeviceSettingServiceConnection( service.registerDeviceSettingsListener(deviceInfo, listener) awaitClose { service.unregisterDeviceSettingsListener(deviceInfo, listener) } } + .catch { e -> + if (e is DeadObjectException) { + Log.e(TAG, "DeadObjectException happens when registering listener.", e) + emit(listOf()) + } else { + throw e + } + } .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList()) } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 83ee9751329fe941646938351a5f60d698d8f974..80e5e59819125a996ec8fe66f027d71f10735881 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -15,6 +15,7 @@ */ package com.android.settingslib.media; +import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO; import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC; import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY; @@ -103,7 +104,8 @@ public class InputMediaDevice extends MediaDevice { TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY, - TYPE_BLUETOOTH_SCO -> + TYPE_BLUETOOTH_SCO, + TYPE_BLE_HEADSET -> true; default -> false; }; @@ -124,7 +126,7 @@ public class InputMediaDevice extends MediaDevice { mProductName != null ? mProductName : mContext.getString(R.string.media_transfer_usb_device_mic_name); - case TYPE_BLUETOOTH_SCO -> + case TYPE_BLUETOOTH_SCO, TYPE_BLE_HEADSET -> mProductName != null ? mProductName : mContext.getString(R.string.media_transfer_bt_device_mic_name); diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt index 7fdbcdae2276e9583505d23e5665fc27ae2b3237..f446bb8e32d1dca0cb07eb5e1d6e3fe1f449f3ee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt @@ -93,33 +93,23 @@ class ZenModeRepositoryImpl( IntentFilter().apply { addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) - if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi()) + if (android.app.Flags.modesApi()) addAction( - NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) + NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED + ) }, /* broadcastPermission = */ null, - /* scheduler = */ if (Flags.volumePanelBroadcastFix()) { - backgroundHandler - } else { - null - }, + /* scheduler = */ backgroundHandler, ) awaitClose { context.unregisterReceiver(receiver) } } - .let { - if (Flags.volumePanelBroadcastFix()) { - // Share the flow to avoid having multiple broadcasts. - it.flowOn(backgroundCoroutineContext) - .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope) - } else { - it.shareIn(started = SharingStarted.WhileSubscribed(), scope = scope) - } - } + .flowOn(backgroundCoroutineContext) + .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope) } override val consolidatedNotificationPolicy: StateFlow by lazy { - if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi()) + if (android.app.Flags.modesApi()) flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) { // If available, get the value from extras to avoid a potential binder call. it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY) @@ -161,11 +151,13 @@ class ZenModeRepositoryImpl( contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE), /* notifyForDescendants= */ false, - observer) + observer, + ) contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG), /* notifyForDescendants= */ false, - observer) + observer, + ) awaitClose { contentResolver.unregisterContentObserver(observer) } } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java index 5eeb49a0b398bdd2f2fdc9c5659169bbf3e3834a..6842d0a949af858ecaf2c49a4bd2054677106867 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java @@ -204,6 +204,13 @@ public class TestModeBuilder { return this; } + public TestModeBuilder implicitForPackage(String pkg) { + setPackage(pkg); + setId(ZenModeConfig.implicitRuleId(pkg)); + setName("Do Not Disturb (" + pkg + ")"); + return this; + } + public TestModeBuilder setActive(boolean active) { if (active) { mConfigZenRule.enabled = true; diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java index 3cc111f6e0998222253be26ff13eb77488256788..d5cfe55813eeed4ecfcce14aed6ea8776e23306d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java @@ -24,7 +24,6 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent; import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime; -import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId; import static android.service.notification.ZenModeConfig.tryParseEventConditionId; import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; @@ -208,6 +207,11 @@ public class ZenMode implements Parcelable { return Strings.nullToEmpty(mRule.getName()); } + @NonNull + public Kind getKind() { + return mKind; + } + @NonNull public Status getStatus() { return mStatus; @@ -224,27 +228,6 @@ public class ZenMode implements Parcelable { return mRule.getTriggerDescription(); } - /** - * Returns a "dynamic" trigger description. For some modes (such as manual Do Not Disturb) - * when activated, we know when (and if) the mode is expected to end on its own; this dynamic - * description reflects that. In other cases, returns {@link #getTriggerDescription}. - */ - @Nullable - public String getDynamicDescription(Context context) { - if (isManualDnd() && isActive()) { - long countdownEndTime = tryParseCountdownConditionId(mRule.getConditionId()); - if (countdownEndTime > 0) { - CharSequence formattedTime = ZenModeConfig.getFormattedTime(context, - countdownEndTime, ZenModeConfig.isToday(countdownEndTime), - context.getUserId()); - return context.getString(com.android.internal.R.string.zen_mode_until, - formattedTime); - } - } - - return getTriggerDescription(); - } - /** * Returns the {@link ZenIcon.Key} corresponding to the icon resource for this mode. This can be * either app-provided (via {@link AutomaticZenRule#setIconResId}, user-chosen (via the icon diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java new file mode 100644 index 0000000000000000000000000000000000000000..f5776989917e22a61b468a60d5c89c9f834ced3a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId; + +import android.content.Context; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenModeConfig; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.common.base.Strings; + +public final class ZenModeDescriptions { + + private final Context mContext; + + public ZenModeDescriptions(@NonNull Context context) { + mContext = context; + } + + /** + * Returns a version of the mode's trigger description that might be "dynamic". + * + *

    For some modes (such as manual Do Not Disturb) when activated, we know when (and if) the + * mode is expected to end on its own; this description reflects that. In other cases, + * returns {@link ZenMode#getTriggerDescription}. + */ + @Nullable + public String getTriggerDescription(@NonNull ZenMode mode) { + if (mode.isManualDnd() && mode.isActive()) { + long countdownEndTime = tryParseCountdownConditionId(mode.getRule().getConditionId()); + if (countdownEndTime > 0) { + CharSequence formattedTime = ZenModeConfig.getFormattedTime(mContext, + countdownEndTime, ZenModeConfig.isToday(countdownEndTime), + mContext.getUserId()); + return mContext.getString(com.android.internal.R.string.zen_mode_until, + formattedTime); + } + } + + return Strings.emptyToNull(mode.getTriggerDescription()); + } + + /** + * Returns a version of the {@link ZenMode} trigger description that is suitable for + * accessibility (for example, where abbreviations are expanded to full words). + * + *

    Returns {@code null} If the standard trigger description (returned by + * {@link #getTriggerDescription}) is sufficient. + */ + @Nullable + public String getTriggerDescriptionForAccessibility(@NonNull ZenMode mode) { + // Only one special case: time-based schedules, where we want to use full day names. + if (mode.isSystemOwned() && mode.getType() == TYPE_SCHEDULE_TIME) { + ZenModeConfig.ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId( + mode.getRule().getConditionId()); + if (schedule != null) { + String fullDaysSummary = SystemZenRules.getDaysOfWeekFull(mContext, schedule); + if (fullDaysSummary != null) { + return fullDaysSummary + ", " + SystemZenRules.getTimeSummary(mContext, + schedule); + } + } + } + + return null; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt index b41e9703d427c6eea3ad21ed356dfd3853d2e907..4c4ce2a61851ed3dbadf3f289fd355f0f8854823 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt @@ -221,6 +221,7 @@ class AudioSharingRepositoryImpl( override suspend fun audioSharingAvailable(): Boolean { return withContext(backgroundCoroutineContext) { BluetoothUtils.isAudioSharingEnabled() + || BluetoothUtils.isAudioSharingPreviewEnabled(contentResolver) } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt index 18a4c6d1748dbb025e7b5555224ff5b872c6bd95..791d86611b069ff8752cd354e2e3983f76a41bea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioSharingLogger.kt @@ -26,4 +26,6 @@ interface AudioSharingLogger { fun onVolumeMapChanged(map: Map) fun onSetDeviceVolumeRequested(volume: Int) + + fun onAudioSharingAvailabilityRequestedError(requestFrom: String, e: String) } \ No newline at end of file diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt index cc4cc8d4ab96803e55adf502f4da4b2e166515cf..731b3a136cd7e40c1351addc72d98eef1994a891 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioSharingRepositoryLogger.kt @@ -43,4 +43,8 @@ class FakeAudioSharingRepositoryLogger : AudioSharingLogger { override fun onSetDeviceVolumeRequested(volume: Int) { mutableLogs.add("onSetVolumeRequested volume=$volume") } + + override fun onAudioSharingAvailabilityRequestedError(requestFrom: String, e: String) { + mutableLogs.add("onAudioSharingAvailabilityRequestedError, requestFrom=$requestFrom") + } } \ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 6d481dbe64e9c9ae2bcece203aca95848aa1857a..fa5d54283a17f5f162528e9d116d1f63e3e81922 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -1256,4 +1256,40 @@ public class BluetoothUtilsTest { assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue(); } + + @Test + public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOff_returnsFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse(); + } + + @Test + public void isAudioSharingHysteresisModeFixAvailable_hysteresisFixFlagOff_returnsFalse() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse(); + } + + @Test + public void isAudioSharingHysteresisModeFixAvailable_previewFlagOn_returnsTrue() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue(); + } + + @Test + public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOn_returnsTrue() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java index 7775b912e51d4ee9d7dac4830f99f976d742cdda..8624c4df833bd67e6219e45b407ba1e8e0a51d25 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java @@ -38,6 +38,7 @@ public class InputMediaDeviceTest { private final int WIRED_HEADSET_ID = 2; private final int USB_HEADSET_ID = 3; private final int BT_HEADSET_ID = 4; + private final int BLE_HEADSET_ID = 5; private final int MAX_VOLUME = 1; private final int CURRENT_VOLUME = 0; private final boolean IS_VOLUME_FIXED = true; @@ -45,6 +46,7 @@ public class InputMediaDeviceTest { private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset"; private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset"; private static final String PRODUCT_NAME_BT_HEADSET = "My Bluetooth Headset"; + private static final String PRODUCT_NAME_BLE_HEADSET = "My BLE Headset"; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -163,4 +165,35 @@ public class InputMediaDeviceTest { assertThat(btMediaDevice.getName()) .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name)); } + + @Test + public void getName_returnCorrectName_bleHeadset() { + InputMediaDevice bleMediaDevice = + InputMediaDevice.create( + mContext, + String.valueOf(BLE_HEADSET_ID), + AudioDeviceInfo.TYPE_BLE_HEADSET, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED, + PRODUCT_NAME_BLE_HEADSET); + assertThat(bleMediaDevice).isNotNull(); + assertThat(bleMediaDevice.getName()).isEqualTo(PRODUCT_NAME_BLE_HEADSET); + } + + @Test + public void getName_returnCorrectName_bleHeadset_nullProductName() { + InputMediaDevice bleMediaDevice = + InputMediaDevice.create( + mContext, + String.valueOf(BLE_HEADSET_ID), + AudioDeviceInfo.TYPE_BLE_HEADSET, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED, + null); + assertThat(bleMediaDevice).isNotNull(); + assertThat(bleMediaDevice.getName()) + .isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt index c136644e095973782579a3ab22146505ca70b2dc..388af61c627392776af23378f92677f71bb1e6bf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt @@ -23,12 +23,10 @@ import android.content.Context import android.content.Intent import android.database.ContentObserver import android.os.Parcelable -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings.Global import androidx.test.filters.SmallTest -import com.android.settingslib.flags.Flags import com.android.settingslib.notification.modes.TestModeBuilder import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.notification.modes.ZenModesBackend @@ -93,26 +91,7 @@ class ZenModeRepositoryTest { ) } - @DisableFlags(Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX) - @Test - fun consolidatedPolicyChanges_repositoryEmits_flagsOff() { - testScope.runTest { - val values = mutableListOf() - `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1) - underTest.consolidatedNotificationPolicy - .onEach { values.add(it) } - .launchIn(backgroundScope) - runCurrent() - - `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy2) - triggerIntent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) - runCurrent() - - assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder() - } - } - - @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX) + @EnableFlags(android.app.Flags.FLAG_MODES_API) @Test fun consolidatedPolicyChanges_repositoryEmits_flagsOn() { testScope.runTest { @@ -131,7 +110,7 @@ class ZenModeRepositoryTest { } } - @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX) + @EnableFlags(android.app.Flags.FLAG_MODES_API) @Test fun consolidatedPolicyChanges_repositoryEmitsFromExtras() { testScope.runTest { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2b3accdbb906a1606e563a082aca500f3af752d7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.notification.modes; + +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.service.notification.SystemZenRules.PACKAGE_ANDROID; + +import static com.google.common.truth.Truth.assertThat; + +import android.service.notification.ZenModeConfig; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.Calendar; + +@RunWith(RobolectricTestRunner.class) +public class ZenModeDescriptionsTest { + + private final ZenModeDescriptions mDescriptions = new ZenModeDescriptions( + RuntimeEnvironment.getApplication()); + + @Test + public void getTriggerDescriptionForAccessibility_scheduleTime_usesFullDays() { + ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); + scheduleInfo.days = new int[] { Calendar.MONDAY }; + scheduleInfo.startHour = 11; + scheduleInfo.endHour = 15; + ZenMode mode = new TestModeBuilder() + .setPackage(PACKAGE_ANDROID) + .setType(TYPE_SCHEDULE_TIME) + .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo)) + .build(); + + assertThat(mDescriptions.getTriggerDescriptionForAccessibility(mode)) + .isEqualTo("Monday, 11:00 AM - 3:00 PM"); + } + + @Test + public void getTriggerDescriptionForAccessibility_otherMode_isNull() { + ZenMode mode = new TestModeBuilder().setTriggerDescription("When December ends").build(); + assertThat(mDescriptions.getTriggerDescriptionForAccessibility(mode)).isNull(); + } +} diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml index 22d02262c3882142cb14f34e31643aef2a73c104..645b275e2af508f3c0ac135085566ddf4aaf698e 100644 --- a/packages/SettingsProvider/res/xml/bookmarks.xml +++ b/packages/SettingsProvider/res/xml/bookmarks.xml @@ -32,6 +32,9 @@ 'y': YouTube --> + diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 8f58e8cd19733b8acd98410e4d392282f3d5531f..c90ba8249d54e41444d00714f84ca90a72b1eb24 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -462,5 +462,7 @@ public class GlobalSettingsValidators { Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_NONE, Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_COMPANION )); + VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR); + VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_NOTIFICATION, ANY_STRING_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 558ccaf7a7072f748665d75b3eaffd12773583eb..509b88b257fef50d26b8cf0d1e1bf2ca8cbe5b8a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -263,6 +263,5 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR); - VALIDATORS.put(System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index fbce6ca07b3ecb4b15fa76ea574d1ff13b31a146..aca2c4ef2a4973a878ea59bda0a155eda59417a3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -16,6 +16,7 @@ package com.android.providers.settings; +import static android.provider.DeviceConfig.DUMP_ARG_NAMESPACE; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; @@ -42,6 +43,7 @@ import android.provider.DeviceConfigShellCommandHandler; import android.provider.Settings; import android.provider.Settings.Config.SyncDisabledMode; import android.provider.UpdatableDeviceConfigServiceReadiness; +import android.util.Log; import android.util.Slog; import com.android.internal.util.FastPrintWriter; @@ -55,11 +57,13 @@ import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Receives shell commands from the command line related to device config flags, and dispatches them @@ -80,6 +84,7 @@ public final class DeviceConfigService extends Binder { final SettingsProvider mProvider; private static final String TAG = "DeviceConfigService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public DeviceConfigService(SettingsProvider provider) { mProvider = provider; @@ -97,14 +102,35 @@ public final class DeviceConfigService extends Binder { } } + // TODO(b/364399200): add unit test @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + String filter = null; if (android.provider.flags.Flags.dumpImprovements()) { - pw.print("SyncDisabledForTests: "); - MyShellCommand.getSyncDisabledForTests(pw, pw); + if (args.length > 0) { + switch (args[0]) { + case DUMP_ARG_NAMESPACE: + if (args.length < 2) { + throw new IllegalArgumentException("argument " + DUMP_ARG_NAMESPACE + + " requires an extra argument"); + } + filter = args[1]; + if (DEBUG) { + Slog.d(TAG, "dump(): setting filter as " + filter); + } + break; + default: + Slog.w(TAG, "dump(): ignoring invalid arguments: " + Arrays.toString(args)); + break; + } + } + if (filter == null) { + pw.print("SyncDisabledForTests: "); + MyShellCommand.getSyncDisabledForTests(pw, pw); - pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); - pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); + pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + } pw.println("DeviceConfig provider: "); try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { @@ -117,8 +143,16 @@ public final class DeviceConfigService extends Binder { IContentProvider iprovider = mProvider.getIContentProvider(); pw.println("DeviceConfig flags:"); + Pattern lineFilter = filter == null ? null : Pattern.compile("^.*" + filter + ".*\\/.*$"); for (String line : MyShellCommand.listAll(iprovider)) { - pw.println(line); + if (lineFilter == null || lineFilter.matcher(line).matches()) { + pw.println(line); + } + } + + if (filter != null) { + // TODO(b/364399200): use filter to skip instead? + return; } ArrayList missingFiles = new ArrayList(); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c2beaa8048f66cd97b57e10b02d457a30280ff59..9de7faf04df60cebe706b8f0b961162afa3f171d 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -564,6 +564,8 @@ public class SettingsBackupTest { Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only + Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device + Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device Settings.Global.Wearable.COMBINED_LOCATION_ENABLE, Settings.Global.Wearable.HAS_PAY_TOKENS, Settings.Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, @@ -944,8 +946,7 @@ public class SettingsBackupTest { Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, Settings.System.WEAR_TTS_PREWARM_ENABLED, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, - Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific - Settings.System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME // internal cache + Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific ); if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) { settings.add(Settings.System.MIN_REFRESH_RATE); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7b6321d1cc7df7892c1914c8f0454dc05673dbcd..526320debb1a3530b5d4b695d1b3cbeca0e5aab1 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -369,6 +369,9 @@ + + + @@ -953,6 +956,13 @@ + + + + + diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS index 8feefa5d892fbc8e3fef9d9020eacc40642144e8..d4b5b86223eadf74f6b2c86696e7af4ca923e966 100644 --- a/packages/Shell/OWNERS +++ b/packages/Shell/OWNERS @@ -12,3 +12,4 @@ patb@google.com cbrubaker@google.com omakoto@google.com michaelwr@google.com +ronish@google.com diff --git a/packages/Shell/aconfig/Android.bp b/packages/Shell/aconfig/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..1d797b29c1a47eeb8fe5e61ad941d36e14d82f7e --- /dev/null +++ b/packages/Shell/aconfig/Android.bp @@ -0,0 +1,13 @@ +aconfig_declarations { + name: "wear_aconfig_declarations", + package: "com.android.shell.flags", + container: "system", + srcs: [ + "wear.aconfig", + ], +} + +java_aconfig_library { + name: "wear_aconfig_declarations_flags_java_lib", + aconfig_declarations: "wear_aconfig_declarations", +} diff --git a/packages/Shell/aconfig/wear.aconfig b/packages/Shell/aconfig/wear.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..e07bd963a4aa6908754a189f6bde45fbe243027a --- /dev/null +++ b/packages/Shell/aconfig/wear.aconfig @@ -0,0 +1,9 @@ +package: "com.android.shell.flags" +container: "system" + +flag { + name: "handle_bugreports_for_wear" + namespace: "wear_services" + description: "This flag enables Shell to propagate bugreport results to WearServices." + bug: "378060870" +} \ No newline at end of file diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 7c478ac78a298778de4c1ffd15767d5a00eb3c10..7f25b51e35cae19dffe80bf7c99021c919460aeb 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -153,6 +153,10 @@ public class BugreportProgressService extends Service { static final String INTENT_BUGREPORT_FINISHED = "com.android.internal.intent.action.BUGREPORT_FINISHED"; + // Intent sent to notify external apps that bugreport aborted due to error. + static final String INTENT_BUGREPORT_ABORTED = + "com.android.internal.intent.action.BUGREPORT_ABORTED"; + // Internal intents used on notification actions. static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL"; static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE"; @@ -174,6 +178,8 @@ public class BugreportProgressService extends Service { static final String EXTRA_INFO = "android.intent.extra.INFO"; static final String EXTRA_EXTRA_ATTACHMENT_URIS = "android.intent.extra.EXTRA_ATTACHMENT_URIS"; + static final String EXTRA_ABORTED_ERROR_CODE = + "android.intent.extra.EXTRA_ABORTED_ERROR_CODE"; private static final ThreadFactory sBugreportManagerCallbackThreadFactory = new ThreadFactory() { @@ -404,6 +410,7 @@ public class BugreportProgressService extends Service { @Override public void onError(@BugreportErrorCode int errorCode) { synchronized (mLock) { + sendBugreportAbortedBroadcastLocked(errorCode); stopProgressLocked(mInfo.id); mInfo.deleteEmptyFiles(); } @@ -460,6 +467,13 @@ public class BugreportProgressService extends Service { onBugreportFinished(mInfo); } } + + @GuardedBy("mLock") + private void sendBugreportAbortedBroadcastLocked(@BugreportErrorCode int errorCode) { + final Intent intent = new Intent(INTENT_BUGREPORT_ABORTED); + intent.putExtra(EXTRA_ABORTED_ERROR_CODE, errorCode); + mContext.sendBroadcast(intent, android.Manifest.permission.DUMP); + } } private void sendRemoteBugreportFinishedBroadcast(Context context, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index bffda8bcae6560efdec5c8c178f33bc98353a4e8..d1a22e8612d38254763faef754bfec482b5e4c39 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -90,8 +90,6 @@ filegroup { "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java", "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java", "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt", "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java", "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java", "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java", @@ -134,7 +132,6 @@ filegroup { "tests/src/**/systemui/accessibility/floatingmenu/MenuViewLayerTest.java", "tests/src/**/systemui/accessibility/floatingmenu/MenuViewTest.java", "tests/src/**/systemui/classifier/PointerCountClassifierTest.java", - "tests/src/**/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt", "tests/src/**/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java", "tests/src/**/systemui/screenrecord/RecordingControllerTest.java", "tests/src/**/systemui/screenshot/RequestProcessorTest.kt", @@ -211,13 +208,13 @@ filegroup { "tests/src/**/systemui/qs/tiles/DreamTileTest.java", "tests/src/**/systemui/qs/FgsManagerControllerTest.java", "tests/src/**/systemui/qs/QSPanelTest.kt", + "tests/src/**/systemui/reardisplay/RearDisplayCoreStartableTest.kt", "tests/src/**/systemui/reardisplay/RearDisplayDialogControllerTest.java", + "tests/src/**/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt", "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java", "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java", - "tests/src/**/systemui/statusbar/notification/PropertyAnimatorTest.java", - "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java", "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java", "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt", "tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java", @@ -233,9 +230,7 @@ filegroup { "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java", "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java", "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt", - "tests/src/**/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt", "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt", - "tests/src/**/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java", "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java", "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt", "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java", @@ -250,7 +245,6 @@ filegroup { "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt", "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt", "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt", - "tests/src/**/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt", "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt", "tests/src/**/systemui/statusbar/policy/CallbackControllerTest.java", "tests/src/**/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java", @@ -263,11 +257,9 @@ filegroup { "tests/src/**/systemui/theme/ThemeOverlayApplierTest.java", "tests/src/**/systemui/touch/TouchInsetManagerTest.java", "tests/src/**/systemui/util/LifecycleFragmentTest.java", - "tests/src/**/systemui/util/TestableAlertDialogTest.kt", "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", "tests/src/**/systemui/util/sensors/AsyncManagerTest.java", "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java", - "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java", "tests/src/**/systemui/volume/VolumeDialogImplTest.java", "tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java", "tests/src/**/systemui/wallet/ui/WalletScreenControllerTest.java", @@ -288,9 +280,6 @@ filegroup { "tests/src/**/systemui/qs/QSImplTest.java", "tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt", "tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt", - "tests/src/**/systemui/screenshot/ActionExecutorTest.kt", - "tests/src/**/systemui/screenshot/ScreenshotDetectionControllerTest.kt", - "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt", "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java", "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java", "tests/src/**/systemui/animation/TransitionAnimatorTest.kt", @@ -303,10 +292,45 @@ filegroup { "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt", "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java", "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java", - "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt", "tests/src/**/systemui/toast/ToastUITest.java", "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt", "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt", + "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt", + ], +} + +// Files which use ExtendedMockito on the device. +filegroup { + name: "SystemUI-tests-broken-robofiles-mockito-extended", + srcs: [ + "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt", + "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt", + "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt", + "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt", + "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt", + "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt", + "tests/src/**/systemui/stylus/StylusManagerTest.kt", + "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt", + "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt", + "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt", + "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java", + "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt", + "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java", + "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt", + "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt", + "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt", + "tests/src/**/systemui/qs/tiles/HotspotTileTest.java", + "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java", + "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java", + "tests/src/**/systemui/wmshell/BubblesTest.java", + "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java", + "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java", + "tests/src/**/systemui/shared/system/RemoteTransitionTest.java", + "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java", + "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java", + "tests/src/**/systemui/ScreenDecorationsTest.java", + "tests/src/**/keyguard/CarrierTextManagerTest.java", + "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java", ], } @@ -314,29 +338,23 @@ filegroup { filegroup { name: "SystemUI-tests-broken-robofiles-compile", srcs: [ - "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java", "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt", "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java", "tests/src/**/systemui/doze/DozeScreenStateTest.java", - "tests/src/**/keyguard/CarrierTextManagerTest.java", "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt", "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt", "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt", "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt", "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt", "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt", "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt", "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt", - "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt", "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt", "tests/src/**/keyguard/ClockEventControllerTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt", - "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt", - "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt", "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt", "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt", @@ -351,7 +369,6 @@ filegroup { "tests/src/**/systemui/controls/ui/SelectionItemTest.kt", "tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt", "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt", "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt", "tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt", "tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt", @@ -417,40 +434,9 @@ filegroup { "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt", "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt", "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt", - "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt", "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt", - "tests/src/**/systemui/ScreenDecorationsTest.java", - "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt", - "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt", - "tests/src/**/systemui/shared/system/RemoteTransitionTest.java", - "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java", - "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt", - "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt", - "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt", - "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt", - "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt", - "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt", - "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java", - "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java", - "tests/src/**/systemui/qs/tiles/HotspotTileTest.java", - "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java", - "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt", - "tests/src/**/systemui/stylus/StylusManagerTest.kt", - "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java", - "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java", "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java", - "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java", - "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt", - "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt", - "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java", - "tests/src/**/systemui/wmshell/BubblesTest.java", - "tests/src/**/systemui/power/PowerUITest.java", - "tests/src/**/systemui/qs/QSSecurityFooterTest.java", - "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java", "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java", - "tests/src/**/systemui/statusbar/CommandQueueTest.java", - "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java", "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java", "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt", ], @@ -497,6 +483,9 @@ android_library { resource_dirs: [], static_libs: [ "//frameworks/libs/systemui:compilelib", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl", "SystemUI-res", "WifiTrackerLib", "WindowManager-Shell", @@ -770,6 +759,9 @@ android_library { ], static_libs: [ "//frameworks/libs/systemui:compilelib", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl", "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", @@ -910,6 +902,7 @@ android_robolectric_test { ":SystemUI-tests-robofiles", ], exclude_srcs: [ + ":SystemUI-tests-broken-robofiles-mockito-extended", ":SystemUI-tests-broken-robofiles-compile", ":SystemUI-tests-broken-robofiles-run", ":SystemUI-tests-broken-robofiles-sysui-run", diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 380344a23c998867739913f63bb0a158f68a408c..e47704ebdf868e4f7dffcf3812af1f631b516b4f 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -153,5 +153,11 @@ { "path": "cts/tests/tests/multiuser" } + ], + + "sysui-e2e-presubmit": [ + { + "name": "PlatformScenarioTests_SysUI_Presubmit" + } ] } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 207ed71c955d5b6e538304d10ec3a524bf811c96..67666c3db81f33a28ea7ee17f499038ef4586fe0 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -267,7 +267,7 @@ flag { flag { name: "dual_shade" namespace: "systemui" - description: "Enables the BC25 Dual Shade (go/bc25-dual-shade-design)." + description: "Enables Dual Shade (go/dual-shade-design-doc)." bug: "337259436" } @@ -557,6 +557,13 @@ flag { bug: "343495953" } +flag { + name: "lockscreen_custom_clocks" + namespace: "systemui" + description: "Enable lockscreen custom clocks" + bug: "378486437" +} + flag { name: "faster_unlock_transition" namespace: "systemui" @@ -625,9 +632,9 @@ flag { flag { name: "status_bar_connected_displays" - namespace: "systemui" + namespace: "lse_desktop_experience" description: "Shows the status bar on connected displays" - bug: "362720336" + bug: "379264862" } flag { @@ -1359,16 +1366,6 @@ flag { } } -flag { - name: "notification_pulsing_fix" - namespace: "systemui" - description: "Allow showing new pulsing notifications when the device is already pulsing." - bug: "335560575" - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { name: "media_lockscreen_launch_animation" namespace : "systemui" @@ -1750,6 +1747,13 @@ flag { bug: "376491880" } +flag { + name: "notification_shade_blur" + namespace: "systemui" + description: "Enables the new blur effect on the Notification Shade." + bug: "370555223" +} + flag { name: "ensure_enr_views_visibility" namespace: "systemui" @@ -1784,3 +1788,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "keyguard_transition_force_finish_on_screen_off" + namespace: "systemui" + description: "Forces KTF transitions to finish if the screen turns all the way off." + bug: "331636736" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 18f40c98fe040a63b9c1f8ccab9cfe54d64806f0..eee0cafd34fe4b091c2355b7460ee608e675626a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -24,18 +24,22 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.graphics.Color import android.graphics.Matrix +import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import android.os.Binder import android.os.Build import android.os.Handler +import android.os.IBinder import android.os.Looper import android.os.RemoteException +import android.util.ArrayMap import android.util.Log import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget +import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup @@ -45,8 +49,12 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.view.animation.PathInterpolator +import android.window.IRemoteTransition +import android.window.IRemoteTransitionFinishedCallback import android.window.RemoteTransition import android.window.TransitionFilter +import android.window.TransitionInfo +import android.window.WindowAnimationState import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread @@ -55,11 +63,14 @@ import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow import com.android.systemui.Flags.translucentOccludingActivityFix +import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations +import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations +import com.android.systemui.animation.TransitionAnimator.Companion.longLivedReturnAnimationsEnabled +import com.android.systemui.animation.TransitionAnimator.Companion.returnAnimationsEnabled import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState -import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions +import com.android.wm.shell.shared.TransitionUtil import java.util.concurrent.Executor import kotlin.math.roundToInt @@ -194,7 +205,13 @@ constructor( private const val LONG_TRANSITION_TIMEOUT = 5_000L private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS) + return TransitionAnimator( + mainExecutor, + TIMINGS, + INTERPOLATORS, + SPRING_TIMINGS, + SPRING_INTERPOLATORS, + ) } private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator { @@ -275,7 +292,7 @@ constructor( "ActivityTransitionAnimator.callback must be set before using this animator" ) val runner = createRunner(controller) - val runnerDelegate = runner.delegate!! + val runnerDelegate = runner.delegate val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the @@ -330,7 +347,11 @@ constructor( // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. if (willAnimate) { - runnerDelegate.postTimeouts() + if (longLivedReturnAnimationsEnabled()) { + runner.postTimeouts() + } else { + runnerDelegate!!.postTimeouts() + } // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { @@ -390,7 +411,7 @@ constructor( launchController: Controller, transitionRegister: TransitionRegister?, ) { - if (!returnAnimationFrameworkLibrary()) return + if (!returnAnimationsEnabled()) return var cleanUpRunnable: Runnable? = null val returnRunner = @@ -413,7 +434,8 @@ constructor( private fun cleanUp() { cleanUpRunnable?.run() } - } + }, + initializeLazily = longLivedReturnAnimationsEnabled(), ) // mTypeSet and mModes match back signals only, and not home. This is on purpose, because @@ -435,7 +457,11 @@ constructor( "${launchController.transitionCookie}_returnTransition", ) - transitionRegister?.register(filter, transition) + transitionRegister?.register( + filter, + transition, + includeTakeover = longLivedReturnAnimationsEnabled(), + ) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } @@ -451,7 +477,10 @@ constructor( /** Create a new animation [Runner] controlled by [controller]. */ @VisibleForTesting - fun createRunner(controller: Controller): Runner { + @JvmOverloads + fun createRunner(controller: Controller, initializeLazily: Boolean = false): Runner { + if (initializeLazily) assertLongLivedReturnAnimations() + // Make sure we use the modified timings when animating a dialog into an app. val transitionAnimator = if (controller.isDialogLaunch) { @@ -460,7 +489,13 @@ constructor( transitionAnimator } - return Runner(controller, callback!!, transitionAnimator, lifecycleListener) + return Runner( + controller, + callback!!, + transitionAnimator, + lifecycleListener, + initializeLazily, + ) } interface PendingIntentStarter { @@ -628,10 +663,7 @@ constructor( * this registration. */ fun register(controller: Controller) { - check(returnAnimationFrameworkLongLived()) { - "Long-lived registrations cannot be used when the returnAnimationFrameworkLongLived " + - "flag is disabled" - } + assertLongLivedReturnAnimations() if (transitionRegister == null) { throw IllegalStateException( @@ -667,10 +699,10 @@ constructor( } val launchRemoteTransition = RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(controller)), + OriginTransition(createRunner(controller, initializeLazily = true)), "${cookie}_launchTransition", ) - transitionRegister.register(launchFilter, launchRemoteTransition) + transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true) val returnController = object : Controller by controller { @@ -689,10 +721,10 @@ constructor( } val returnRemoteTransition = RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(returnController)), + OriginTransition(createRunner(returnController, initializeLazily = true)), "${cookie}_returnTransition", ) - transitionRegister.register(returnFilter, returnRemoteTransition) + transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true) longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition) } @@ -738,14 +770,154 @@ constructor( } } + /** [Runner] wrapper that supports animation takeovers. */ + private inner class OriginTransition(private val runner: Runner) : IRemoteTransition { + private val delegate = RemoteAnimationRunnerCompat.wrap(runner) + + init { + assertLongLivedReturnAnimations() + } + + override fun startAnimation( + token: IBinder?, + info: TransitionInfo?, + t: SurfaceControl.Transaction?, + finishCallback: IRemoteTransitionFinishedCallback?, + ) { + delegate.startAnimation(token, info, t, finishCallback) + } + + override fun mergeAnimation( + transition: IBinder?, + info: TransitionInfo?, + t: SurfaceControl.Transaction?, + mergeTarget: IBinder?, + finishCallback: IRemoteTransitionFinishedCallback?, + ) { + delegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback) + } + + override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) { + delegate.onTransitionConsumed(transition, aborted) + } + + override fun takeOverAnimation( + token: IBinder?, + info: TransitionInfo?, + t: SurfaceControl.Transaction?, + finishCallback: IRemoteTransitionFinishedCallback?, + states: Array, + ) { + if (info == null || t == null) { + Log.e( + TAG, + "Skipping the animation takeover because the required data is missing: " + + "info=$info, transaction=$t", + ) + return + } + + // The following code converts the contents of the given TransitionInfo into + // RemoteAnimationTargets. This is necessary because we must currently support both the + // new (Shell, remote transitions) and old (remote animations) framework to maintain + // functionality for all users of the library. + val apps = ArrayList() + val filteredStates = ArrayList() + val leashMap = ArrayMap() + val leafTaskFilter = TransitionUtil.LeafTaskFilter() + + // About layering: we divide up the "layer space" into 2 regions (each the size of the + // change count). This lets us categorize things into above and below while + // maintaining their relative ordering. + val belowLayers = info.changes.size + val aboveLayers = info.changes.size * 2 + for (i in info.changes.indices) { + val change = info.changes[i] + if (change == null || change.taskInfo == null) { + continue + } + + val taskInfo = change.taskInfo + + if (TransitionUtil.isWallpaper(change)) { + val target = + TransitionUtil.newTarget( + change, + belowLayers - i, // wallpapers go into the "below" layer space + info, + t, + leashMap, + ) + + // Make all the wallpapers opaque. + t.setAlpha(target.leash, 1f) + } else if (leafTaskFilter.test(change)) { + // Start by putting everything into the "below" layer space. + val target = + TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap) + apps.add(target) + filteredStates.add(states[i]) + + // Make all the apps opaque. + t.setAlpha(target.leash, 1f) + + if ( + TransitionUtil.isClosingType(change.mode) && + taskInfo?.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME + ) { + // Raise closing task to "above" layer so it isn't covered. + t.setLayer(target.leash, aboveLayers - i) + } + } else if (TransitionInfo.isIndependent(change, info)) { + // Root tasks + if (TransitionUtil.isClosingType(change.mode)) { + // Raise closing task to "above" layer so it isn't covered. + t.setLayer(change.leash, aboveLayers - i) + } else if (TransitionUtil.isOpeningType(change.mode)) { + // Put into the "below" layer space. + t.setLayer(change.leash, belowLayers - i) + } + } else if (TransitionUtil.isDividerBar(change)) { + val target = + TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap) + apps.add(target) + filteredStates.add(states[i]) + } + } + + val wrappedCallback: IRemoteAnimationFinishedCallback = + object : IRemoteAnimationFinishedCallback.Stub() { + override fun onAnimationFinished() { + leashMap.clear() + val finishTransaction = SurfaceControl.Transaction() + finishCallback?.onTransitionFinished(null, finishTransaction) + finishTransaction.close() + } + } + + runner.takeOverAnimation( + apps.toTypedArray(), + filteredStates.toTypedArray(), + t, + wrappedCallback, + ) + } + + override fun asBinder(): IBinder { + return delegate.asBinder() + } + } + @VisibleForTesting inner class Runner( - controller: Controller, - callback: Callback, + private val controller: Controller, + private val callback: Callback, /** The animator to use to animate the window transition. */ - transitionAnimator: TransitionAnimator, + private val transitionAnimator: TransitionAnimator, /** Listener for animation lifecycle events. */ - listener: Listener? = null, + private val listener: Listener? = null, + /** Whether the internal [delegate] should be initialized lazily. */ + private val initializeLazily: Boolean = false, ) : IRemoteAnimationRunner.Stub() { // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might @@ -753,15 +925,14 @@ constructor( @VisibleForTesting var delegate: AnimationDelegate? init { - delegate = - AnimationDelegate( - mainExecutor, - controller, - callback, - DelegatingAnimationCompletionListener(listener, this::dispose), - transitionAnimator, - disableWmTimeout, - ) + delegate = null + if (!initializeLazily) { + // Ephemeral launches bundle the runner with the launch request (instead of being + // registered ahead of time for later use). This means that there could be a timeout + // between creation and invocation, so the delegate needs to exist from the + // beginning in order to handle such timeout. + createDelegate() + } } @BinderThread @@ -772,6 +943,36 @@ constructor( nonApps: Array?, finishedCallback: IRemoteAnimationFinishedCallback?, ) { + initAndRun(finishedCallback) { delegate -> + delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) + } + } + + @VisibleForTesting + @BinderThread + fun takeOverAnimation( + apps: Array?, + windowAnimationStates: Array, + startTransaction: SurfaceControl.Transaction, + finishedCallback: IRemoteAnimationFinishedCallback?, + ) { + assertLongLivedReturnAnimations() + initAndRun(finishedCallback) { delegate -> + delegate.takeOverAnimation( + apps, + windowAnimationStates, + startTransaction, + finishedCallback, + ) + } + } + + @BinderThread + private fun initAndRun( + finishedCallback: IRemoteAnimationFinishedCallback?, + performAnimation: (AnimationDelegate) -> Unit, + ) { + maybeSetUp() val delegate = delegate mainExecutor.execute { if (delegate == null) { @@ -780,7 +981,7 @@ constructor( // signal back that we're done with it. finishedCallback?.onAnimationFinished() } else { - delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) + performAnimation(delegate) } } } @@ -794,6 +995,32 @@ constructor( } } + @VisibleForTesting + @UiThread + fun postTimeouts() { + maybeSetUp() + delegate?.postTimeouts() + } + + @AnyThread + private fun maybeSetUp() { + if (!initializeLazily || delegate != null) return + createDelegate() + } + + @AnyThread + private fun createDelegate() { + delegate = + AnimationDelegate( + mainExecutor, + controller, + callback, + DelegatingAnimationCompletionListener(listener, this::dispose), + transitionAnimator, + disableWmTimeout, + ) + } + @AnyThread fun dispose() { // Drop references to animation controller once we're done with the animation @@ -867,7 +1094,7 @@ constructor( init { // We do this check here to cover all entry points, including Launcher which doesn't // call startIntentWithAnimation() - if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag() + if (!controller.isLaunching) assertReturnAnimations() } @UiThread @@ -893,19 +1120,73 @@ constructor( nonApps: Array?, callback: IRemoteAnimationFinishedCallback?, ) { + val window = setUpAnimation(apps, callback) ?: return + + if (controller.windowAnimatorState == null || !longLivedReturnAnimationsEnabled()) { + val navigationBar = + nonApps?.firstOrNull { + it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + } + + startAnimation(window, navigationBar, iCallback = callback) + } else { + // If a [controller.windowAnimatorState] exists, treat this like a takeover. + takeOverAnimationInternal( + window, + startWindowStates = null, + startTransaction = null, + callback, + ) + } + } + + @UiThread + internal fun takeOverAnimation( + apps: Array?, + startWindowStates: Array, + startTransaction: SurfaceControl.Transaction, + callback: IRemoteAnimationFinishedCallback?, + ) { + val window = setUpAnimation(apps, callback) ?: return + takeOverAnimationInternal(window, startWindowStates, startTransaction, callback) + } + + private fun takeOverAnimationInternal( + window: RemoteAnimationTarget, + startWindowStates: Array?, + startTransaction: SurfaceControl.Transaction?, + callback: IRemoteAnimationFinishedCallback?, + ) { + val useSpring = + !controller.isLaunching && startWindowStates != null && startTransaction != null + startAnimation( + window, + navigationBar = null, + useSpring, + startWindowStates, + startTransaction, + callback, + ) + } + + @UiThread + private fun setUpAnimation( + apps: Array?, + callback: IRemoteAnimationFinishedCallback?, + ): RemoteAnimationTarget? { removeTimeouts() // The animation was started too late and we already notified the controller that it // timed out. if (timedOut) { callback?.invoke() - return + return null } // This should not happen, but let's make sure we don't start the animation if it was // cancelled before and we already notified the controller. if (cancelled) { - return + return null } val window = findTargetWindowIfPossible(apps) @@ -921,15 +1202,10 @@ constructor( } controller.onTransitionAnimationCancelled() listener?.onTransitionAnimationCancelled() - return + return null } - val navigationBar = - nonApps?.firstOrNull { - it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - } - - startAnimation(window, navigationBar, callback) + return window } private fun findTargetWindowIfPossible( @@ -950,7 +1226,7 @@ constructor( for (it in apps) { if (it.mode == targetMode) { if (activityTransitionUseLargestWindow()) { - if (returnAnimationFrameworkLibrary()) { + if (returnAnimationsEnabled()) { // If the controller contains a cookie, _only_ match if either the // candidate contains the matching cookie, or a component is also // defined and is a match. @@ -995,8 +1271,11 @@ constructor( private fun startAnimation( window: RemoteAnimationTarget, - navigationBar: RemoteAnimationTarget?, - iCallback: IRemoteAnimationFinishedCallback?, + navigationBar: RemoteAnimationTarget? = null, + useSpring: Boolean = false, + startingWindowStates: Array? = null, + startTransaction: SurfaceControl.Transaction? = null, + iCallback: IRemoteAnimationFinishedCallback? = null, ) { if (TransitionAnimator.DEBUG) { Log.d(TAG, "Remote animation started") @@ -1046,18 +1325,66 @@ constructor( val controller = object : Controller by delegate { override fun createAnimatorState(): TransitionAnimator.State { - if (isLaunching) return delegate.createAnimatorState() - return delegate.windowAnimatorState?.toTransitionState() - ?: getWindowRadius(isExpandingFullyAbove).let { - TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right, - topCornerRadius = it, - bottomCornerRadius = it, - ) + if (isLaunching) { + return delegate.createAnimatorState() + } else if (!longLivedReturnAnimationsEnabled()) { + return delegate.windowAnimatorState?.toTransitionState() + ?: getWindowRadius(isExpandingFullyAbove).let { + TransitionAnimator.State( + top = windowBounds.top, + bottom = windowBounds.bottom, + left = windowBounds.left, + right = windowBounds.right, + topCornerRadius = it, + bottomCornerRadius = it, + ) + } + } + + // The states are sorted matching the changes inside the transition info. + // Using this info, the RemoteAnimationTargets are created, with their + // prefixOrderIndex fields in reverse order to that of changes. To extract + // the right state, we need to invert again. + val windowState = + if (startingWindowStates != null) { + startingWindowStates[ + startingWindowStates.size - window.prefixOrderIndex] + } else { + controller.windowAnimatorState } + + // TODO(b/323863002): use the timestamp and velocity to update the initial + // position. + val bounds = windowState?.bounds + val left: Int = bounds?.left?.toInt() ?: windowBounds.left + val top: Int = bounds?.top?.toInt() ?: windowBounds.top + val right: Int = bounds?.right?.toInt() ?: windowBounds.right + val bottom: Int = bounds?.bottom?.toInt() ?: windowBounds.bottom + + val width = windowBounds.right - windowBounds.left + val height = windowBounds.bottom - windowBounds.top + // Scale the window. We use the max of (widthRatio, heightRatio) so that + // there is no blank space on any side. + val widthRatio = (right - left).toFloat() / width + val heightRatio = (bottom - top).toFloat() / height + val startScale = maxOf(widthRatio, heightRatio) + + val maybeRadius = windowState?.topLeftRadius + val windowRadius = + if (maybeRadius != null) { + maybeRadius * startScale + } else { + getWindowRadius(isExpandingFullyAbove) + } + + return TransitionAnimator.State( + top = top, + bottom = bottom, + left = left, + right = right, + topCornerRadius = windowRadius, + bottomCornerRadius = windowRadius, + ) } override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { @@ -1071,6 +1398,19 @@ constructor( "[controller=$delegate]", ) } + + if (startTransaction != null) { + // Calling applyStateToWindow() here avoids skipping a frame when taking + // over an animation. + applyStateToWindow( + window, + createAnimatorState(), + linearProgress = 0f, + useSpring, + startTransaction, + ) + } + delegate.onTransitionAnimationStart(isExpandingFullyAbove) } @@ -1094,14 +1434,29 @@ constructor( progress: Float, linearProgress: Float, ) { - applyStateToWindow(window, state, linearProgress) + applyStateToWindow(window, state, linearProgress, useSpring) navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } listener?.onTransitionAnimationProgress(linearProgress) delegate.onTransitionAnimationProgress(state, progress, linearProgress) } } - + val windowState = + if (startingWindowStates != null) { + startingWindowStates[startingWindowStates.size - window.prefixOrderIndex] + } else { + controller.windowAnimatorState + } + val velocityPxPerS = + if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) { + val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000 + val yVelocityPxPerS = windowState.velocityPxPerMs.y * 1000 + PointF(xVelocityPxPerS, yVelocityPxPerS) + } else if (useSpring) { + PointF(0f, 0f) + } else { + null + } animation = transitionAnimator.startAnimation( controller, @@ -1109,6 +1464,7 @@ constructor( windowBackgroundColor, fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, drawHole = !controller.isBelowAnimatingWindow, + startVelocity = velocityPxPerS, ) } @@ -1128,6 +1484,8 @@ constructor( window: RemoteAnimationTarget, state: TransitionAnimator.State, linearProgress: Float, + useSpring: Boolean, + transaction: SurfaceControl.Transaction? = null, ) { if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) { // Don't apply any transaction if the view root we synchronize with was detached or @@ -1171,25 +1529,47 @@ constructor( windowCropF.bottom.roundToInt(), ) - val windowAnimationDelay = + val interpolators: TransitionAnimator.Interpolators + val windowProgress: Float + + if (useSpring) { + val windowAnimationDelay: Float + val windowAnimationDuration: Float if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDelay + windowAnimationDelay = SPRING_TIMINGS.contentAfterFadeInDelay + windowAnimationDuration = SPRING_TIMINGS.contentAfterFadeInDuration } else { - TIMINGS.contentBeforeFadeOutDelay + windowAnimationDelay = SPRING_TIMINGS.contentBeforeFadeOutDelay + windowAnimationDuration = SPRING_TIMINGS.contentBeforeFadeOutDuration } - val windowAnimationDuration = + + interpolators = SPRING_INTERPOLATORS + windowProgress = + TransitionAnimator.getProgress( + linearProgress, + windowAnimationDelay, + windowAnimationDuration, + ) + } else { + val windowAnimationDelay: Long + val windowAnimationDuration: Long if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDuration + windowAnimationDelay = TIMINGS.contentAfterFadeInDelay + windowAnimationDuration = TIMINGS.contentAfterFadeInDuration } else { - TIMINGS.contentBeforeFadeOutDuration + windowAnimationDelay = TIMINGS.contentBeforeFadeOutDelay + windowAnimationDuration = TIMINGS.contentBeforeFadeOutDuration } - val windowProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - windowAnimationDelay, - windowAnimationDuration, - ) + + interpolators = INTERPOLATORS + windowProgress = + TransitionAnimator.getProgress( + TIMINGS, + linearProgress, + windowAnimationDelay, + windowAnimationDuration, + ) + } // The alpha of the opening window. If it opens above the expandable, then it should // fade in progressively. Otherwise, it should be fully opaque and will be progressively @@ -1197,12 +1577,12 @@ constructor( val alpha = if (controller.isBelowAnimatingWindow) { if (controller.isLaunching) { - INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation( + interpolators.contentAfterFadeInInterpolator.getInterpolation( windowProgress ) } else { 1 - - INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation( + interpolators.contentBeforeFadeOutInterpolator.getInterpolation( windowProgress ) } @@ -1216,6 +1596,7 @@ constructor( // especially important for lock screen animations, where the window is not clipped by // the shade. val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale + val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) .withAlpha(alpha) @@ -1223,11 +1604,15 @@ constructor( .withWindowCrop(windowCrop) .withCornerRadius(cornerRadius) .withVisibility(true) - .build() + if (transaction != null) params.withMergeTransaction(transaction) - transactionApplier.scheduleApply(params) + transactionApplier.scheduleApply(params.build()) } + // TODO(b/377643129): remote transitions have no way of identifying the navbar when + // converting to RemoteAnimationTargets (and in my testing it was never included in the + // transition at all). So this method is not used anymore. Remove or adapt once we fully + // convert to remote transitions. private fun applyStateToNavigationBar( navigationBar: RemoteAnimationTarget, state: TransitionAnimator.State, @@ -1362,9 +1747,17 @@ constructor( } /** Register [remoteTransition] with WM Shell using the given [filter]. */ - internal fun register(filter: TransitionFilter, remoteTransition: RemoteTransition) { + internal fun register( + filter: TransitionFilter, + remoteTransition: RemoteTransition, + includeTakeover: Boolean, + ) { shellTransitions?.registerRemote(filter, remoteTransition) iShellTransitions?.registerRemote(filter, remoteTransition) + if (includeTakeover) { + shellTransitions?.registerRemoteForTakeover(filter, remoteTransition) + iShellTransitions?.registerRemoteForTakeover(filter, remoteTransition) + } } /** Unregister [remoteTransition] from WM Shell. */ diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index fdb4871423c34632c297b9451731e9fef297db16..de4bdbc284c410180a66c2cccbdf22fc03db54a5 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary +import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived import java.util.concurrent.Executor import kotlin.math.abs import kotlin.math.max @@ -113,13 +114,26 @@ class TransitionAnimator( ) } - internal fun checkReturnAnimationFrameworkFlag() { - check(returnAnimationFrameworkLibrary()) { - "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " + - "disabled" + internal fun assertReturnAnimations() { + check(returnAnimationsEnabled()) { + "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " + + "is disabled" } } + internal fun returnAnimationsEnabled() = returnAnimationFrameworkLibrary() + + internal fun assertLongLivedReturnAnimations() { + check(longLivedReturnAnimationsEnabled()) { + "Long-lived registrations cannot be used when the " + + "returnAnimationFrameworkLibrary or the " + + "returnAnimationFrameworkLongLived flag are disabled" + } + } + + internal fun longLivedReturnAnimationsEnabled() = + returnAnimationFrameworkLibrary() && returnAnimationFrameworkLongLived() + internal fun WindowAnimationState.toTransitionState() = State().also { bounds?.let { b -> @@ -467,7 +481,8 @@ class TransitionAnimator( drawHole: Boolean = false, startVelocity: PointF? = null, ): Animation { - if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag() + if (!controller.isLaunching) assertReturnAnimations() + if (startVelocity != null) assertLongLivedReturnAnimations() // We add an extra layer with the same color as the dialog/app splash screen background // color, which is usually the same color of the app background. We first fade in this layer diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp index 91dc3e338c59e3453b5d229c4573de333c7535f2..9f1598364dbc4d2e0c4c850bc561c196bec6de74 100644 --- a/packages/SystemUI/common/Android.bp +++ b/packages/SystemUI/common/Android.bp @@ -30,5 +30,9 @@ java_library { "src/**/*.kt", ], + static_libs: ["SystemUI-shared-utils"], + + libs: ["//frameworks/libs/systemui:tracinglib-platform"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt similarity index 100% rename from packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt rename to packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt similarity index 100% rename from packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt rename to packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt index d31d7aa5948985c337013f430c50e573cf5af8cd..71ec63c1666c3d41b7d1e10ed3e9134f803d4754 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt @@ -63,6 +63,9 @@ private fun platformColorScheme(isDarkTheme: Boolean, context: Context): ColorSc return if (isDarkTheme) { dynamicDarkColorScheme(context) .copy( + inverseSurface = color(context, R.color.system_inverse_surface_dark), + inverseOnSurface = color(context, R.color.system_inverse_on_surface_dark), + inversePrimary = color(context, R.color.system_inverse_primary_dark), error = color(context, R.color.system_error_dark), onError = color(context, R.color.system_on_error_dark), errorContainer = color(context, R.color.system_error_container_dark), @@ -71,6 +74,9 @@ private fun platformColorScheme(isDarkTheme: Boolean, context: Context): ColorSc } else { dynamicLightColorScheme(context) .copy( + inverseSurface = color(context, R.color.system_inverse_surface_light), + inverseOnSurface = color(context, R.color.system_inverse_on_surface_light), + inversePrimary = color(context, R.color.system_inverse_primary_light), error = color(context, R.color.system_error_light), onError = color(context, R.color.system_on_error_light), errorContainer = color(context, R.color.system_error_container_light), diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt index de021a0677cf5915c953acd40b625124640ad292..737853b88f7a237b0a8d8564f1d883fb14bf58d7 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/theme/PlatformThemeTest.kt @@ -89,7 +89,7 @@ class PlatformThemeTest { addValue( "inversePrimary", colorScheme.inversePrimary, - R.attr.materialColorPrimaryInverse, + R.attr.materialColorInversePrimary, ) addValue("secondary", colorScheme.secondary, R.attr.materialColorSecondary) addValue("onSecondary", colorScheme.onSecondary, R.attr.materialColorOnSecondary) @@ -131,12 +131,12 @@ class PlatformThemeTest { addValue( "inverseSurface", colorScheme.inverseSurface, - R.attr.materialColorSurfaceInverse, + R.attr.materialColorInverseSurface, ) addValue( "inverseOnSurface", colorScheme.inverseOnSurface, - R.attr.materialColorOnSurfaceInverse, + R.attr.materialColorInverseOnSurface, ) addValue("error", colorScheme.error, R.attr.materialColorError) addValue("onError", colorScheme.onError, R.attr.materialColorOnError) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 9392b1afffa37b0dc44b05c1bbb8124b678c8769..778d7e7f99b361429089e093d3c6397d6b1f37a9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -66,13 +66,14 @@ constructor( interactionHandler = interactionHandler, dialogFactory = dialogFactory, widgetSection = widgetSection, - modifier = Modifier.element(Communal.Elements.Grid) + modifier = Modifier.element(Communal.Elements.Grid), + sceneScope = this@Content, ) } with(lockSection) { LockIcon( overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.element(Communal.Elements.LockIcon) + modifier = Modifier.element(Communal.Elements.LockIcon), ) } with(bottomAreaSection) { @@ -80,17 +81,13 @@ constructor( Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() ) } - } + }, ) { measurables, constraints -> val communalGridMeasurable = measurables[0] val lockIconMeasurable = measurables[1] val bottomAreaMeasurable = measurables[2] - val noMinConstraints = - constraints.copy( - minWidth = 0, - minHeight = 0, - ) + val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) val lockIconBounds = @@ -109,14 +106,8 @@ constructor( ) layout(constraints.maxWidth, constraints.maxHeight) { - communalGridPlaceable.place( - x = 0, - y = 0, - ) - lockIconPlaceable.place( - x = lockIconBounds.left, - y = lockIconBounds.top, - ) + communalGridPlaceable.place(x = 0, y = 0) + lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top) bottomAreaPlaceable.place( x = 0, y = constraints.maxHeight - bottomAreaPlaceable.height, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 5e1ac1f3035412ccd06f118562ae3217c810a538..5b1203fa17c9d2c36e055b43aff762cc65de2798 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -163,6 +163,7 @@ import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.animation.Easings.Emphasized +import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.internal.R.dimen.system_app_widget_background_radius @@ -186,7 +187,9 @@ import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlin.math.max import kotlin.math.min @@ -205,6 +208,7 @@ fun CommunalHub( widgetConfigurator: WidgetConfigurator? = null, onOpenWidgetPicker: (() -> Unit)? = null, onEditDone: (() -> Unit)? = null, + sceneScope: SceneScope? = null, ) { val communalContent by viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) @@ -414,6 +418,7 @@ fun CommunalHub( widgetConfigurator = widgetConfigurator, interactionHandler = interactionHandler, widgetSection = widgetSection, + sceneScope = sceneScope, ) } } @@ -735,6 +740,7 @@ private fun BoxScope.CommunalHubLazyGrid( widgetConfigurator: WidgetConfigurator?, interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, + sceneScope: SceneScope?, ) { var gridModifier = Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) } @@ -807,7 +813,6 @@ private fun BoxScope.CommunalHubLazyGrid( ) { ResizeableItemFrameViewModel() } - if (viewModel.isEditMode && dragDropState != null) { val isItemDragging = dragDropState.draggingItemKey == item.key val outlineAlpha by @@ -872,6 +877,7 @@ private fun BoxScope.CommunalHubLazyGrid( interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, + sceneScope = sceneScope, ) } } @@ -1096,6 +1102,7 @@ private fun CommunalContent( interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, resizeableItemFrameViewModel: ResizeableItemFrameViewModel, + sceneScope: SceneScope? = null, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> @@ -1119,7 +1126,7 @@ private fun CommunalContent( is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) - is CommunalContentModel.Umo -> Umo(viewModel, modifier) + is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier) } } @@ -1530,7 +1537,25 @@ private fun TutorialContent(modifier: Modifier = Modifier) { } @Composable -private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) { +private fun Umo( + viewModel: BaseCommunalViewModel, + sceneScope: SceneScope?, + modifier: Modifier = Modifier, +) { + if (SceneContainerFlag.isEnabled && sceneScope != null) { + sceneScope.MediaCarousel( + modifier = modifier.fillMaxSize(), + isVisible = true, + mediaHost = viewModel.mediaHost, + carouselController = viewModel.mediaCarouselController, + ) + } else { + UmoLegacy(viewModel, modifier) + } +} + +@Composable +private fun UmoLegacy(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) { AndroidView( modifier = modifier.pointerInput(Unit) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt index 6e30575a684d0806e496b86733d11bf7a4f8e2f0..16002bc709fd97a1288a73a889936db998c16d82 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -59,7 +59,14 @@ internal constructor( private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit, private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit, private val onReorderWidgets: (widgetIdToRankMap: Map) -> Unit, - private val onResizeWidget: (id: Int, spanY: Int, widgetIdToRankMap: Map) -> Unit, + private val onResizeWidget: + ( + id: Int, + spanY: Int, + widgetIdToRankMap: Map, + componentName: ComponentName, + rank: Int, + ) -> Unit, ) { var list = communalContent.toMutableStateList() private set @@ -105,7 +112,9 @@ internal constructor( } else { emptyMap() } - onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap) + val componentName = item.componentName + val rank = item.rank + onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap, componentName, rank) } /** diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 1551ca97a2e3a0e81bd4d40249ed98b1aec151b5..ef5e90bd7aad4b4ba8f56bb3844ba93d0c4cf094 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -179,11 +179,18 @@ internal constructor( val targetItem = if (communalWidgetResizing()) { state.layoutInfo.visibleItemsInfo.findLast { item -> + val lastVisibleItemIndex = state.layoutInfo.visibleItemsInfo.last().index val itemBoundingBox = IntRect(item.offset, item.size) draggingItemKey != item.key && contentListState.isItemEditable(item.index) && (draggingBoundingBox.contains(itemBoundingBox.center) || - itemBoundingBox.contains(draggingBoundingBox.center)) + itemBoundingBox.contains(draggingBoundingBox.center)) && + // If we swap with the last visible item, and that item doesn't fit + // in the gap created by moving the current item, then the current item + // will get placed after the last visible item. In this case, it gets + // placed outside of the viewport. We avoid this here, so the user + // has to scroll first before the swap can happen. + (item.index != lastVisibleItemIndex || item.span <= draggingItem.span) } } else { state.layoutInfo.visibleItemsInfo diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 20c8c6a3f4bfd5a1c4195e1bed465a079cc4a0dd..2d32fd768eaac409d1a317c6353d70c1c72408fb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor @@ -84,6 +85,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonVi import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.qs.ui.composable.QuickSettingsTheme +import com.android.systemui.qs.ui.compose.borderOnFocus import com.android.systemui.res.R import kotlinx.coroutines.launch @@ -264,7 +266,11 @@ private fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = color = colorAttr(model.backgroundColor), shape = CircleShape, onClick = model.onClick, - modifier = modifier, + modifier = + modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + CornerSize(percent = 50), + ), ) { val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified Icon(model.icon, tint = tint, modifier = Modifier.size(20.dp)) @@ -291,7 +297,11 @@ private fun NumberButton( shape = CircleShape, onClick = onClick, interactionSource = interactionSource, - modifier = modifier, + modifier = + modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + CornerSize(percent = 50), + ), ) { Box(Modifier.size(40.dp)) { Box( @@ -342,7 +352,10 @@ private fun TextButton( color = colorAttr(R.attr.underSurface), contentColor = MaterialTheme.colorScheme.onSurfaceVariant, borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)), - modifier = modifier.padding(horizontal = 4.dp), + modifier = + modifier + .padding(horizontal = 4.dp) + .borderOnFocus(color = MaterialTheme.colorScheme.secondary, CornerSize(50)), onClick = onClick, ) { Row( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 2cde6787f730ca84543fa816d14acfa2e4d52cc6..9de7a5d659ae9c98221d123ff74a732f3ce13666 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -15,7 +15,10 @@ import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransit import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition +import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransition +import com.android.systemui.scene.ui.composable.transitions.dreamToCommunalTransition import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition +import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition @@ -55,7 +58,10 @@ val SceneContainerTransitions = transitions { // Scene transitions from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() } + from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() } + from(Scenes.Dream, to = Scenes.Communal) { dreamToCommunalTransition() } from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() } + from(Scenes.Dream, to = Scenes.Shade) { dreamToShadeTransition() } from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() } from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() } from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt new file mode 100644 index 0000000000000000000000000000000000000000..8cb89f91a831f2504cced335e85546465c194e3e --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.dreamToBouncerTransition() { + toBouncerTransition() +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt new file mode 100644 index 0000000000000000000000000000000000000000..93c10b6224abfc508ca864681c3c252f0a37b321 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToCommunalTransition.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.scene.ui.composable.transitions + +import androidx.compose.animation.core.tween +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.communal.ui.compose.AllElements +import com.android.systemui.communal.ui.compose.Communal + +fun TransitionBuilder.dreamToCommunalTransition() { + spec = tween(durationMillis = 1000) + + // Translate communal hub grid from the end direction. + translate(Communal.Elements.Grid, Edge.End) + + // Fade all communal hub elements. + timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt new file mode 100644 index 0000000000000000000000000000000000000000..e35aaae19e297f6e9024a77a2a895483710a17da --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.dreamToShadeTransition(durationScale: Double = 1.0) { + toShadeTransition(durationScale = durationScale) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt index 71fa6c9e567a493068a98f7fb4f6703de77a4081..ce7a85b19fb4f48ee5e4e9eeb84b1810fb21bb06 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -19,11 +19,8 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance -import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings @@ -31,24 +28,17 @@ import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.goneToSplitShadeTransition( - durationScale: Double = 1.0, -) { +fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) - distance = - object : UserActionDistance { - override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - return fromSceneSize.height.toFloat() * 2 / 3f - } - } + distance = UserActionDistance { fromContent, _, _ -> + val fromContentSize = checkNotNull(fromContent.targetSize()) + fromContentSize.height.toFloat() * 2 / 3f + } fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt index 1486ea7d53b5b5c8555da0070e29e45bb3593e6e..1f7a7380bbc68c3f9d9d56a15f6740fb439e60fe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt @@ -19,35 +19,25 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance -import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.lockscreenToSplitShadeTransition( - durationScale: Double = 1.0, -) { +fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) - distance = - object : UserActionDistance { - override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - return fromSceneSize.height.toFloat() * 2 / 3f - } - } + distance = UserActionDistance { fromContent, _, _ -> + val fromContentSize = checkNotNull(fromContent.targetSize()) + fromContentSize.height.toFloat() * 2 / 3f + } fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt index 8cd357ef92a5565650027c04fecbce719b33f2e1..ba1972fbcc5a49f2c1789c616a6396509d2c9f66 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt @@ -1,46 +1,36 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance -import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.shadeToQuickSettingsTransition( - durationScale: Double = 1.0, -) { +fun TransitionBuilder.shadeToQuickSettingsTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - distance = - object : UserActionDistance { - override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - val distance = - Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y - ?: return 0f - return fromSceneSize.height - distance - } - } + distance = UserActionDistance { fromContent, _, _ -> + val distance = + Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y + ?: return@UserActionDistance 0f + val fromContentSize = checkNotNull(fromContent.targetSize()) + fromContentSize.height - distance + } translate(Notifications.Elements.NotificationScrim, Edge.Bottom) timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) } translate( ShadeHeader.Elements.CollapsedContentStart, - y = ShadeHeader.Dimensions.CollapsedHeight + y = ShadeHeader.Dimensions.CollapsedHeight, ) translate(ShadeHeader.Elements.CollapsedContentEnd, y = ShadeHeader.Dimensions.CollapsedHeight) translate( ShadeHeader.Elements.ExpandedContent, - y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight) + y = -(ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight), ) translate(ShadeHeader.Elements.ShadeCarrierGroup, y = -ShadeHeader.Dimensions.CollapsedHeight) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt index de76f708c1c75ab145ceb699a7756859c1585dc1..d35537a74c8535412fcf4ce7d720d423d2f665ed 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToBouncerTransition.kt @@ -28,8 +28,9 @@ private const val TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f fun TransitionBuilder.toBouncerTransition() { spec = tween(durationMillis = 500) - distance = UserActionDistance { fromSceneSize, _ -> - fromSceneSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION + distance = UserActionDistance { fromContent, _, _ -> + val fromContentSize = checkNotNull(fromContent.targetSize()) + fromContentSize.height * TO_BOUNCER_SWIPE_DISTANCE_FRACTION } translate(Bouncer.Elements.Content, y = 300.dp) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt index 55fa6ad94ed3916d05bc3c1767e8f799c00f0aba..e78bc6afcc4fa68755428e706ad03884e66957e4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt @@ -33,7 +33,10 @@ fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0 stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) - distance = UserActionDistance { fromSceneSize, _ -> fromSceneSize.height.toFloat() * 2 / 3f } + distance = UserActionDistance { fromContent, _, _ -> + val fromContentSize = checkNotNull(fromContent.targetSize()) + fromContentSize.height.toFloat() * 2 / 3f + } translate(OverlayShade.Elements.Panel, Edge.Top) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt index b677dff2dcf98ca5713fe56fb998088b6d4e576d..bfae4897dc688e6f10c22a6427399737c2e2d058 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt @@ -19,12 +19,9 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.compose.animation.scene.UserActionDistance -import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.qs.ui.composable.QuickSettings @@ -33,24 +30,16 @@ import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.toShadeTransition( - durationScale: Double = 1.0, -) { +fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) - distance = - object : UserActionDistance { - override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - return Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f - } - } + distance = UserActionDistance { _, _, _ -> + Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f + } fractionRange(start = .58f) { fade(ShadeHeader.Elements.Clock) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt index 352d29e2198776abb1ccb1c960f791bb555b791f..74896482be88710388f2951219e264503f83d34d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.offset -import androidx.compose.ui.util.fastFirst import androidx.compose.ui.util.fastFirstOrNull import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy.LayoutId import kotlin.math.max @@ -60,18 +59,20 @@ class SingleShadeMeasurePolicy( val shadeHeaderPlaceable = measurables - .fastFirst { it.layoutId == LayoutId.ShadeHeader } - .measure(constraintsWithCutout) + .fastFirstOrNull { it.layoutId == LayoutId.ShadeHeader } + ?.measure(constraintsWithCutout) val mediaPlaceable = measurables .fastFirstOrNull { it.layoutId == LayoutId.Media } ?.measure(applyMediaConstraints(constraintsWithCutout, isMediaInRow)) val quickSettingsPlaceable = measurables - .fastFirst { it.layoutId == LayoutId.QuickSettings } - .measure(constraintsWithCutout) + .fastFirstOrNull { it.layoutId == LayoutId.QuickSettings } + ?.measure(constraintsWithCutout) val notificationsPlaceable = - measurables.fastFirst { it.layoutId == LayoutId.Notifications }.measure(constraints) + measurables + .fastFirstOrNull { it.layoutId == LayoutId.Notifications } + ?.measure(constraints) val notificationsTop = calculateNotificationsTop( @@ -84,23 +85,25 @@ class SingleShadeMeasurePolicy( onNotificationsTopChanged(notificationsTop) return layout(constraints.maxWidth, constraints.maxHeight) { - shadeHeaderPlaceable.placeRelative(x = insetsLeft, y = insetsTop) - quickSettingsPlaceable.placeRelative( + shadeHeaderPlaceable?.placeRelative(x = insetsLeft, y = insetsTop) + val statusBarHeaderHeight = shadeHeaderPlaceable?.height ?: 0 + quickSettingsPlaceable?.placeRelative( x = insetsLeft, - y = insetsTop + shadeHeaderPlaceable.height, + y = insetsTop + statusBarHeaderHeight, ) - if (mediaPlaceable != null) + if (mediaPlaceable != null) { + val quickSettingsHeight = quickSettingsPlaceable?.height ?: 0 + if (isMediaInRow) { // mediaPlaceable height ranges from 0 to qsHeight. We want it to be centered // vertically when it's smaller than the QS - val mediaCenteringOffset = - (quickSettingsPlaceable.height - mediaPlaceable.height) / 2 + val mediaCenteringOffset = (quickSettingsHeight - mediaPlaceable.height) / 2 mediaPlaceable.placeRelative( x = insetsLeft + constraintsWithCutout.maxWidth / 2, y = insetsTop + - shadeHeaderPlaceable.height + + statusBarHeaderHeight + mediaCenteringOffset + mediaOffset(), zIndex = mediaZIndex(), @@ -108,30 +111,34 @@ class SingleShadeMeasurePolicy( } else { mediaPlaceable.placeRelative( x = insetsLeft, - y = insetsTop + shadeHeaderPlaceable.height + quickSettingsPlaceable.height, + y = insetsTop + statusBarHeaderHeight + quickSettingsHeight, zIndex = mediaZIndex(), ) } + } // Notifications don't need to accommodate for horizontal insets - notificationsPlaceable.placeRelative(x = 0, y = notificationsTop) + notificationsPlaceable?.placeRelative(x = 0, y = notificationsTop) } } private fun calculateNotificationsTop( - statusBarHeaderPlaceable: Placeable, - quickSettingsPlaceable: Placeable, + statusBarHeaderPlaceable: Placeable?, + quickSettingsPlaceable: Placeable?, mediaPlaceable: Placeable?, insetsTop: Int, isMediaInRow: Boolean, ): Int { val mediaHeight = mediaPlaceable?.height ?: 0 + val statusBarHeaderHeight = statusBarHeaderPlaceable?.height ?: 0 + val quickSettingsHeight = quickSettingsPlaceable?.height ?: 0 + return insetsTop + - statusBarHeaderPlaceable.height + + statusBarHeaderHeight + if (isMediaInRow) { - max(quickSettingsPlaceable.height, mediaHeight) + max(quickSettingsHeight, mediaHeight) } else { - quickSettingsPlaceable.height + mediaHeight + quickSettingsHeight + mediaHeight } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt index 163f4b36f4722721609af45c161b3765e4e47d20..78e605601b76df62da30302c1b74a2fb25d25b19 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt @@ -307,6 +307,6 @@ private fun draggableTopPadding(): Dp { private object DraggableBottomSheet { val DefaultTopPadding = 64.dp - val LargeScreenTopPadding = 72.dp + val LargeScreenTopPadding = 56.dp val MaxWidth = 640.dp } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 041cd15bdeea6752b27314a0b4190f2658ca0599..04c527176ccec821a8a08c6ce7e55645e230c864 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.util.fastCoerceIn import com.android.compose.animation.scene.content.Content -import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import com.android.compose.nestedscroll.OnStopScope import com.android.compose.nestedscroll.PriorityNestedScrollConnection @@ -36,11 +35,11 @@ internal typealias SuspendedValue = suspend () -> T internal interface DraggableHandler { /** - * Start a drag with the given [pointersInfo] and [overSlop]. + * Start a drag with the given [pointersDown] and [overSlop]. * * The returned [DragController] should be used to continue or stop the drag. */ - fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController + fun onDragStarted(pointersDown: PointersInfo.PointersDown?, overSlop: Float): DragController } /** @@ -95,7 +94,7 @@ internal class DraggableHandlerImpl( * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f, * indicating that the transition should be intercepted. */ - internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean { + internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean { // We don't intercept the touch if we are not currently driving the transition. val dragController = dragController if (dragController?.isDrivingTransition != true) { @@ -106,7 +105,7 @@ internal class DraggableHandlerImpl( // Only intercept the current transition if one of the 2 swipes results is also a transition // between the same pair of contents. - val swipes = computeSwipes(pointersInfo) + val swipes = computeSwipes(pointersDown) val fromContent = layoutImpl.content(swipeAnimation.currentContent) val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent) val currentScene = layoutImpl.state.currentScene @@ -123,7 +122,10 @@ internal class DraggableHandlerImpl( )) } - override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController { + override fun onDragStarted( + pointersDown: PointersInfo.PointersDown?, + overSlop: Float, + ): DragController { if (overSlop == 0f) { val oldDragController = dragController check(oldDragController != null && oldDragController.isDrivingTransition) { @@ -148,7 +150,7 @@ internal class DraggableHandlerImpl( return updateDragController(swipes, swipeAnimation) } - val swipes = computeSwipes(pointersInfo) + val swipes = computeSwipes(pointersDown) val fromContent = layoutImpl.contentForUserActions() swipes.updateSwipesResults(fromContent) @@ -194,11 +196,11 @@ internal class DraggableHandlerImpl( ) } - private fun computeSwipes(pointersInfo: PointersInfo?): Swipes { - val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) } + private fun computeSwipes(pointersDown: PointersInfo.PointersDown?): Swipes { + val fromSource = pointersDown?.let { resolveSwipeSource(it.startedPosition) } return Swipes( - upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource), - downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource), + upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersDown, fromSource), + downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersDown, fromSource), ) } } @@ -206,7 +208,7 @@ internal class DraggableHandlerImpl( private fun resolveSwipe( orientation: Orientation, isUpOrLeft: Boolean, - pointersInfo: PointersInfo?, + pointersDown: PointersInfo.PointersDown?, fromSource: SwipeSource.Resolved?, ): Swipe.Resolved { return Swipe.Resolved( @@ -227,9 +229,9 @@ private fun resolveSwipe( } }, // If the number of pointers is not specified, 1 is assumed. - pointerCount = pointersInfo?.pointersDown ?: 1, + pointerCount = pointersDown?.count ?: 1, // Resolves the pointer type only if all pointers are of the same type. - pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(), + pointersType = pointersDown?.countByType?.keys?.singleOrNull(), fromSource = fromSource, ) } @@ -528,82 +530,62 @@ internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resol } internal class NestedScrollHandlerImpl( - private val layoutImpl: SceneTransitionLayoutImpl, - private val orientation: Orientation, + private val draggableHandler: DraggableHandlerImpl, internal var topOrLeftBehavior: NestedScrollBehavior, internal var bottomOrRightBehavior: NestedScrollBehavior, internal var isExternalOverscrollGesture: () -> Boolean, private val pointersInfoOwner: PointersInfoOwner, ) { - private val layoutState = layoutImpl.state - private val draggableHandler = layoutImpl.draggableHandler(orientation) - val connection: PriorityNestedScrollConnection = nestedScrollConnection() - private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved { - return resolveSwipe( - orientation = draggableHandler.orientation, - isUpOrLeft = isUpOrLeft, - pointersInfo = pointersInfo, - fromSource = - pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) }, - ) - } - private fun nestedScrollConnection(): PriorityNestedScrollConnection { // If we performed a long gesture before entering priority mode, we would have to avoid // moving on to the next scene. var canChangeScene = false - var lastPointersInfo: PointersInfo? = null - - fun hasNextScene(amount: Float): Boolean { - val transitionState = layoutState.transitionState - val scene = transitionState.currentScene - val fromScene = layoutImpl.scene(scene) - val resolvedSwipe = - when { - amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo) - amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo) - else -> null - } - val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) } - if (nextScene != null) return true + var lastPointersDown: PointersInfo.PointersDown? = null - if (transitionState !is TransitionState.Idle) return false - - val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation) - return overscrollSpec != null + fun shouldEnableSwipes(): Boolean { + return draggableHandler.layoutImpl + .contentForUserActions() + .shouldEnableSwipes(draggableHandler.orientation) } var isIntercepting = false return PriorityNestedScrollConnection( - orientation = orientation, + orientation = draggableHandler.orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> - val pointersInfo = pointersInfoOwner.pointersInfo() + val pointersDown: PointersInfo.PointersDown? = + when (val info = pointersInfoOwner.pointersInfo()) { + PointersInfo.MouseWheel -> { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + + is PointersInfo.PointersDown -> info + null -> null + } + canChangeScene = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val canInterceptSwipeTransition = canChangeScene && offsetAvailable != 0f && - draggableHandler.shouldImmediatelyIntercept(pointersInfo) + draggableHandler.shouldImmediatelyIntercept(pointersDown) if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false + val layoutImpl = draggableHandler.layoutImpl val threshold = layoutImpl.transitionInterceptionThreshold - val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold) + val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold) if (hasSnappedToIdle) { // If the current swipe transition is closed to 0f or 1f, then we want to // interrupt the transition (snapping it to Idle) and scroll the list. return@PriorityNestedScrollConnection false } - if (pointersInfo?.isMouseWheel == true) { - // Do not support mouse wheel interactions - return@PriorityNestedScrollConnection false - } - lastPointersInfo = pointersInfo + lastPointersDown = pointersDown // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -622,28 +604,33 @@ internal class NestedScrollHandlerImpl( val isZeroOffset = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f - val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo?.isMouseWheel == true) { - // Do not support mouse wheel interactions - return@PriorityNestedScrollConnection false - } - lastPointersInfo = pointersInfo + val pointersDown: PointersInfo.PointersDown? = + when (val info = pointersInfoOwner.pointersInfo()) { + PointersInfo.MouseWheel -> { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + + is PointersInfo.PointersDown -> info + null -> null + } + lastPointersDown = pointersDown val canStart = when (behavior) { NestedScrollBehavior.EdgeNoPreview -> { canChangeScene = isZeroOffset - isZeroOffset && hasNextScene(offsetAvailable) + isZeroOffset && shouldEnableSwipes() } NestedScrollBehavior.EdgeWithPreview -> { canChangeScene = isZeroOffset - hasNextScene(offsetAvailable) + shouldEnableSwipes() } NestedScrollBehavior.EdgeAlways -> { canChangeScene = true - hasNextScene(offsetAvailable) + shouldEnableSwipes() } } @@ -664,14 +651,19 @@ internal class NestedScrollHandlerImpl( // We could start an overscroll animation canChangeScene = false - val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo?.isMouseWheel == true) { - // Do not support mouse wheel interactions - return@PriorityNestedScrollConnection false - } - lastPointersInfo = pointersInfo + val pointersDown: PointersInfo.PointersDown? = + when (val info = pointersInfoOwner.pointersInfo()) { + PointersInfo.MouseWheel -> { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + + is PointersInfo.PointersDown -> info + null -> null + } + lastPointersDown = pointersDown - val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) + val canStart = behavior.canStartOnPostFling && shouldEnableSwipes() if (canStart) { isIntercepting = false } @@ -679,11 +671,10 @@ internal class NestedScrollHandlerImpl( canStart }, onStart = { firstScroll -> - val pointersInfo = lastPointersInfo scrollController( dragController = draggableHandler.onDragStarted( - pointersInfo = pointersInfo, + pointersDown = lastPointersDown, overSlop = if (isIntercepting) 0f else firstScroll, ), canChangeScene = canChangeScene, @@ -701,8 +692,7 @@ private fun scrollController( ): ScrollController { return object : ScrollController { override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float { - val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo?.isMouseWheel == true) { + if (pointersInfoOwner.pointersInfo() == PointersInfo.MouseWheel) { // Do not support mouse wheel interactions return 0f } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index d976e8e62f3cc88231546e0c1bc831d7d274cf16..eb2a01632095ce4ef5ac0c0fd91678fd07c185e6 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -50,6 +50,8 @@ import androidx.compose.ui.util.fastForEachReversed import androidx.compose.ui.util.lerp import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.CustomPropertyTransformation +import com.android.compose.animation.scene.transformation.InterpolatedPropertyTransformation import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.TransformationWithRange @@ -348,8 +350,7 @@ internal class ElementNode( val placeInThisContent = elementContentWhenIdle( layoutImpl, - currentState.currentScene, - currentState.currentOverlays, + currentState, isInContent = { it in element.stateByContent }, ) == content.key @@ -637,20 +638,11 @@ internal inline fun elementState( internal inline fun elementContentWhenIdle( layoutImpl: SceneTransitionLayoutImpl, - idle: TransitionState.Idle, - isInContent: (ContentKey) -> Boolean, -): ContentKey { - val currentScene = idle.currentScene - val overlays = idle.currentOverlays - return elementContentWhenIdle(layoutImpl, currentScene, overlays, isInContent) -} - -private inline fun elementContentWhenIdle( - layoutImpl: SceneTransitionLayoutImpl, - currentScene: SceneKey, - overlays: Set, + currentState: TransitionState, isInContent: (ContentKey) -> Boolean, ): ContentKey { + val currentScene = currentState.currentScene + val overlays = currentState.currentOverlays if (overlays.isEmpty()) { return currentScene } @@ -1308,7 +1300,14 @@ private inline fun computeValue( checkNotNull(if (currentContent == toContent) toState else fromState) val idleValue = contentValue(overscrollState) val targetValue = - with(propertySpec.transformation) { + with( + propertySpec.transformation.requireInterpolatedTransformation( + element, + transition, + ) { + "Custom transformations in overscroll specs should not be possible" + } + ) { layoutImpl.propertyTransformationScope.transform( currentContent, element.key, @@ -1390,7 +1389,7 @@ private inline fun computeValue( // fromContent or toContent during interruptions. val content = contentState.content - val transformation = + val transformationWithRange = transformation(transition.transformationSpec.transformations(element.key, content)) val previewTransformation = @@ -1403,7 +1402,14 @@ private inline fun computeValue( val idleValue = contentValue(contentState) val isEntering = content == toContent val previewTargetValue = - with(previewTransformation.transformation) { + with( + previewTransformation.transformation.requireInterpolatedTransformation( + element, + transition, + ) { + "Custom transformations in preview specs should not be possible" + } + ) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1413,8 +1419,15 @@ private inline fun computeValue( } val targetValueOrNull = - transformation?.let { transformation -> - with(transformation.transformation) { + transformationWithRange?.let { transformation -> + with( + transformation.transformation.requireInterpolatedTransformation( + element, + transition, + ) { + "Custom transformations are not allowed for properties with a preview" + } + ) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1461,7 +1474,7 @@ private inline fun computeValue( lerp( lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress), idleValue, - transformation?.range?.progress(transition.progress) ?: transition.progress, + transformationWithRange?.range?.progress(transition.progress) ?: transition.progress, ) } else { if (targetValueOrNull == null) { @@ -1474,22 +1487,39 @@ private inline fun computeValue( lerp( lerp(idleValue, previewTargetValue, previewRangeProgress), targetValueOrNull, - transformation.range?.progress(transition.progress) ?: transition.progress, + transformationWithRange.range?.progress(transition.progress) + ?: transition.progress, ) } } } - if (transformation == null) { + if (transformationWithRange == null) { // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout // pass). return currentValue() } + val transformation = transformationWithRange.transformation + when (transformation) { + is CustomPropertyTransformation -> + return with(transformation) { + layoutImpl.propertyTransformationScope.transform( + content, + element.key, + transition, + transition.coroutineScope, + ) + } + is InterpolatedPropertyTransformation -> { + /* continue */ + } + } + val idleValue = contentValue(contentState) val targetValue = - with(transformation.transformation) { + with(transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1506,7 +1536,7 @@ private inline fun computeValue( val progress = transition.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. - val rangeProgress = transformation.range?.progress(progress) ?: progress + val rangeProgress = transformationWithRange.range?.progress(progress) ?: progress // Interpolate between the value at rest and the value before entering/after leaving. val isEntering = @@ -1523,6 +1553,22 @@ private inline fun computeValue( } } +private inline fun PropertyTransformation.requireInterpolatedTransformation( + element: Element, + transition: TransitionState.Transition, + errorMessage: () -> String, +): InterpolatedPropertyTransformation { + return when (this) { + is InterpolatedPropertyTransformation -> this + is CustomPropertyTransformation -> { + val elem = element.key.debugName + val fromContent = transition.fromContent + val toContent = transition.toContent + error("${errorMessage()} (element=$elem fromContent=$fromContent toContent=$toContent)") + } + } +} + private inline fun interpolateSharedElement( transition: TransitionState.Transition, contentValue: (Element.State) -> T, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index c790ff03aef154ed47d723534334ff7642dc1270..509a16c5a7048fc0ea5f83985f7b8807caaaecf9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -188,7 +188,9 @@ private fun shouldComposeMovableElement( return when ( val elementState = movableElementState(element, layoutImpl.state.transitionStates) ) { - null -> false + null -> + movableElementContentWhenIdle(layoutImpl, element, layoutImpl.state.transitionState) == + content is TransitionState.Idle -> movableElementContentWhenIdle(layoutImpl, element, elementState) == content is TransitionState.Transition -> { @@ -217,7 +219,7 @@ private fun movableElementState( private fun movableElementContentWhenIdle( layoutImpl: SceneTransitionLayoutImpl, element: MovableElementKey, - elementState: TransitionState.Idle, + elementState: TransitionState, ): ContentKey { val contents = element.contentPicker.contents return elementContentWhenIdle(layoutImpl, elementState, isInContent = { contents.contains(it) }) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index ab2324a87d8145690abdf598c2cc4a83fad238a5..160326792f81c21f9b6570bb444dbfc343479190 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -80,8 +80,8 @@ import kotlinx.coroutines.launch @Stable internal fun Modifier.multiPointerDraggable( orientation: Orientation, - startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, - onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, + startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, + onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, dispatcher: NestedScrollDispatcher, @@ -99,8 +99,9 @@ internal fun Modifier.multiPointerDraggable( private data class MultiPointerDraggableElement( private val orientation: Orientation, - private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, - private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, + private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, + private val onDragStarted: + (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, private val onFirstPointerDown: () -> Unit, private val swipeDetector: SwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -126,8 +127,8 @@ private data class MultiPointerDraggableElement( internal class MultiPointerDraggableNode( orientation: Orientation, - var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, - var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, + var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, + var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -185,20 +186,27 @@ internal class MultiPointerDraggableNode( private var lastPointerEvent: PointerEvent? = null private var startedPosition: Offset? = null - private var pointersDown: Int = 0 + private var countPointersDown: Int = 0 internal fun pointersInfo(): PointersInfo? { - val startedPosition = startedPosition - val lastPointerEvent = lastPointerEvent - if (startedPosition == null || lastPointerEvent == null) { - // This may be null, i.e. when the user uses TalkBack - return null - } + // This may be null, i.e. when the user uses TalkBack + val lastPointerEvent = lastPointerEvent ?: return null + + if (lastPointerEvent.type == PointerEventType.Scroll) return PointersInfo.MouseWheel - return PointersInfo( + val startedPosition = startedPosition ?: return null + + return PointersInfo.PointersDown( startedPosition = startedPosition, - pointersDown = pointersDown, - lastPointerEvent = lastPointerEvent, + count = countPointersDown, + countByType = + buildMap { + lastPointerEvent.changes.fastForEach { change -> + if (!change.pressed) return@fastForEach + val newValue = (get(change.type) ?: 0) + 1 + put(change.type, newValue) + } + }, ) } @@ -218,11 +226,11 @@ internal class MultiPointerDraggableNode( val changes = pointerEvent.changes lastPointerEvent = pointerEvent - pointersDown = changes.countDown() + countPointersDown = changes.countDown() when { // There are no more pointers down. - pointersDown == 0 -> { + countPointersDown == 0 -> { startedPosition = null // In case of multiple events with 0 pointers down (not pressed) we may have @@ -290,8 +298,8 @@ internal class MultiPointerDraggableNode( detectDragGestures( orientation = orientation, startDragImmediately = startDragImmediately, - onDragStart = { pointersInfo, overSlop -> - onDragStarted(pointersInfo, overSlop) + onDragStart = { pointersDown, overSlop -> + onDragStarted(pointersDown, overSlop) }, onDrag = { controller, amount -> dispatchScrollEvents( @@ -440,8 +448,8 @@ internal class MultiPointerDraggableNode( */ private suspend fun AwaitPointerEventScope.detectDragGestures( orientation: Orientation, - startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, - onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, + startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean, + onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, onDragCancel: (controller: DragController) -> Unit, @@ -466,13 +474,14 @@ internal class MultiPointerDraggableNode( .first() var overSlop = 0f - var lastPointersInfo = + var lastPointersDown: PointersInfo.PointersDown = checkNotNull(pointersInfo()) { "We should have pointers down, last event: $currentEvent" } + as PointersInfo.PointersDown val drag = - if (startDragImmediately(lastPointersInfo)) { + if (startDragImmediately(lastPointersDown)) { consumablePointer.consume() consumablePointer } else { @@ -499,10 +508,11 @@ internal class MultiPointerDraggableNode( ) } ?: return - lastPointersInfo = + lastPointersDown = checkNotNull(pointersInfo()) { "We should have pointers down, last event: $currentEvent" } + as PointersInfo.PointersDown // Make sure that overSlop is not 0f. This can happen when the user drags by exactly // the touch slop. However, the overSlop we pass to onDragStarted() is used to // compute the direction we are dragging in, so overSlop should never be 0f unless @@ -516,7 +526,7 @@ internal class MultiPointerDraggableNode( drag } - val controller = onDragStart(lastPointersInfo, overSlop) + val controller = onDragStart(lastPointersDown, overSlop) val successful: Boolean try { @@ -666,48 +676,31 @@ internal fun interface PointersInfoOwner { fun pointersInfo(): PointersInfo? } -/** - * Holds information about pointer interactions within a composable. - * - * This class stores details such as the starting position of a gesture, the number of pointers - * down, and whether the last pointer event was a mouse wheel scroll. - * - * @param startedPosition The starting position of the gesture. This is the position where the first - * pointer touched the screen, not necessarily the point where dragging begins. This may be - * different from the initial touch position if a child composable intercepts the gesture before - * this one. - * @param pointersDown The number of pointers currently down. - * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll. - * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type - * currently down/pressed. - */ -internal data class PointersInfo( - val startedPosition: Offset, - val pointersDown: Int, - val isMouseWheel: Boolean, - val pointersDownByType: Map, -) { - init { - check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" } +internal sealed interface PointersInfo { + /** + * Holds information about pointer interactions within a composable. + * + * This class stores details such as the starting position of a gesture, the number of pointers + * down, and whether the last pointer event was a mouse wheel scroll. + * + * @param startedPosition The starting position of the gesture. This is the position where the + * first pointer touched the screen, not necessarily the point where dragging begins. This may + * be different from the initial touch position if a child composable intercepts the gesture + * before this one. + * @param count The number of pointers currently down. + * @param countByType Provide a map of pointer types to the count of pointers of that type + * currently down/pressed. + */ + data class PointersDown( + val startedPosition: Offset, + val count: Int, + val countByType: Map, + ) : PointersInfo { + init { + check(count > 0) { "We should have at least 1 pointer down, $count instead" } + } } -} -private fun PointersInfo( - startedPosition: Offset, - pointersDown: Int, - lastPointerEvent: PointerEvent, -): PointersInfo { - return PointersInfo( - startedPosition = startedPosition, - pointersDown = pointersDown, - isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll, - pointersDownByType = - buildMap { - lastPointerEvent.changes.fastForEach { change -> - if (!change.pressed) return@fastForEach - val newValue = (get(change.type) ?: 0) + 1 - put(change.type, newValue) - } - }, - ) + /** Indicates whether the last pointer event was a mouse wheel scroll. */ + data object MouseWheel : PointersInfo } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index fbd1cd542c0546ef2072d65fe8b1da6146247ded..955be603efafdadb1900eb9b0deb93996e1d926f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene -import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection @@ -69,32 +68,28 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { } internal fun Modifier.nestedScrollToScene( - layoutImpl: SceneTransitionLayoutImpl, - orientation: Orientation, + draggableHandler: DraggableHandlerImpl, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, isExternalOverscrollGesture: () -> Boolean, ) = this then NestedScrollToSceneElement( - layoutImpl = layoutImpl, - orientation = orientation, + draggableHandler = draggableHandler, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, isExternalOverscrollGesture = isExternalOverscrollGesture, ) private data class NestedScrollToSceneElement( - private val layoutImpl: SceneTransitionLayoutImpl, - private val orientation: Orientation, + private val draggableHandler: DraggableHandlerImpl, private val topOrLeftBehavior: NestedScrollBehavior, private val bottomOrRightBehavior: NestedScrollBehavior, private val isExternalOverscrollGesture: () -> Boolean, ) : ModifierNodeElement() { override fun create() = NestedScrollToSceneNode( - layoutImpl = layoutImpl, - orientation = orientation, + draggableHandler = draggableHandler, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, isExternalOverscrollGesture = isExternalOverscrollGesture, @@ -102,8 +97,7 @@ private data class NestedScrollToSceneElement( override fun update(node: NestedScrollToSceneNode) { node.update( - layoutImpl = layoutImpl, - orientation = orientation, + draggableHandler = draggableHandler, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, isExternalOverscrollGesture = isExternalOverscrollGesture, @@ -112,16 +106,14 @@ private data class NestedScrollToSceneElement( override fun InspectorInfo.inspectableProperties() { name = "nestedScrollToScene" - properties["layoutImpl"] = layoutImpl - properties["orientation"] = orientation + properties["draggableHandler"] = draggableHandler properties["topOrLeftBehavior"] = topOrLeftBehavior properties["bottomOrRightBehavior"] = bottomOrRightBehavior } } private class NestedScrollToSceneNode( - private var layoutImpl: SceneTransitionLayoutImpl, - private var orientation: Orientation, + private var draggableHandler: DraggableHandlerImpl, private var topOrLeftBehavior: NestedScrollBehavior, private var bottomOrRightBehavior: NestedScrollBehavior, private var isExternalOverscrollGesture: () -> Boolean, @@ -129,12 +121,8 @@ private class NestedScrollToSceneNode( private var scrollBehaviorOwner: ScrollBehaviorOwner? = null private fun findScrollBehaviorOwner(): ScrollBehaviorOwner? { - var behaviorOwner = scrollBehaviorOwner - if (behaviorOwner == null) { - behaviorOwner = findScrollBehaviorOwner(layoutImpl.draggableHandler(orientation)) - scrollBehaviorOwner = behaviorOwner - } - return behaviorOwner + return scrollBehaviorOwner + ?: findScrollBehaviorOwner(draggableHandler).also { scrollBehaviorOwner = it } } private val updateScrollBehaviorsConnection = @@ -177,14 +165,12 @@ private class NestedScrollToSceneNode( } fun update( - layoutImpl: SceneTransitionLayoutImpl, - orientation: Orientation, + draggableHandler: DraggableHandlerImpl, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, isExternalOverscrollGesture: () -> Boolean, ) { - this.layoutImpl = layoutImpl - this.orientation = orientation + this.draggableHandler = draggableHandler this.topOrLeftBehavior = topOrLeftBehavior this.bottomOrRightBehavior = bottomOrRightBehavior this.isExternalOverscrollGesture = isExternalOverscrollGesture diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index dbf7d7b298342f3a34d19c60d4e8855e0e5e01fb..d3ddb5003469d60ec6ddeae92af4ddbaa182b3ae 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -637,8 +637,8 @@ sealed class UserActionResult( fun interface UserActionDistance { /** - * Return the **absolute** distance of the user action given the size of the scene we are - * animating from and the [orientation]. + * Return the **absolute** distance of the user action when going from [fromContent] to + * [toContent] in the given [orientation]. * * Note: This function will be called for each drag event until it returns a value > 0f. This * for instance allows you to return 0f or a negative value until the first layout pass of a @@ -646,7 +646,8 @@ fun interface UserActionDistance { * transitioning to when computing this absolute distance. */ fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, + fromContent: ContentKey, + toContent: ContentKey, orientation: Orientation, ): Float } @@ -656,7 +657,8 @@ interface UserActionDistanceScope : Density, ElementStateScope /** The user action has a fixed [absoluteDistance]. */ class FixedDistance(private val distance: Dp) : UserActionDistance { override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, + fromContent: ContentKey, + toContent: ContentKey, orientation: Orientation, ): Float = distance.toPx() } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index e93cf8f714cd08fd327770565b7e8fd760760fe8..b916b0b45e41a7a388147737443e4ac43f596630 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -125,8 +125,8 @@ internal class SceneTransitionLayoutImpl( } // TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed. - private val horizontalDraggableHandler: DraggableHandlerImpl - private val verticalDraggableHandler: DraggableHandlerImpl + internal val horizontalDraggableHandler: DraggableHandlerImpl + internal val verticalDraggableHandler: DraggableHandlerImpl internal val elementStateScope = ElementStateScopeImpl(this) internal val propertyTransformationScope = PropertyTransformationScopeImpl(this) @@ -163,12 +163,6 @@ internal class SceneTransitionLayoutImpl( state.checkThread() } - internal fun draggableHandler(orientation: Orientation): DraggableHandlerImpl = - when (orientation) { - Orientation.Vertical -> verticalDraggableHandler - Orientation.Horizontal -> horizontalDraggableHandler - } - internal fun scene(key: SceneKey): Scene { return scenes[key] ?: error("Scene $key is not configured") } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 61332b61ed1b21fd09fafae9ffaa54ff898986cf..b3fd097946d081c8a34182b5e7d70b87c021d994 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -25,17 +25,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny -import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastForEach import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.SharedElementTransformation -import com.android.compose.animation.scene.transition.link.LinkedTransition -import com.android.compose.animation.scene.transition.link.StateLink import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch /** @@ -236,7 +233,6 @@ fun MutableSceneTransitionLayoutState( canShowOverlay: (OverlayKey) -> Boolean = { true }, canHideOverlay: (OverlayKey) -> Boolean = { true }, canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, - stateLinks: List = emptyList(), ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, @@ -246,7 +242,6 @@ fun MutableSceneTransitionLayoutState( canShowOverlay, canHideOverlay, canReplaceOverlay, - stateLinks, ) } @@ -258,10 +253,7 @@ internal class MutableSceneTransitionLayoutStateImpl( internal val canChangeScene: (SceneKey) -> Boolean = { true }, internal val canShowOverlay: (OverlayKey) -> Boolean = { true }, internal val canHideOverlay: (OverlayKey) -> Boolean = { true }, - internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> - true - }, - private val stateLinks: List = emptyList(), + internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, ) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() @@ -275,11 +267,12 @@ internal class MutableSceneTransitionLayoutStateImpl( private set /** - * The flattened list of [SharedElementTransformation] within all the transitions in + * The flattened list of [SharedElementTransformation.Factory] within all the transitions in * [transitionStates]. */ - private val transformationsWithElevation: List by derivedStateOf { - transformationsWithElevation(transitionStates) + private val transformationFactoriesWithElevation: + List by derivedStateOf { + transformationFactoriesWithElevation(transitionStates) } override val currentScene: SceneKey @@ -361,29 +354,27 @@ internal class MutableSceneTransitionLayoutStateImpl( } override suspend fun startTransition(transition: TransitionState.Transition, chain: Boolean) { + Log.i(TAG, "startTransition(transition=$transition, chain=$chain)") checkThread() - try { - // Keep a reference to the previous transition (if any). - val previousTransition = currentTransition + // Prepare the transition before starting it. This is outside of the try/finally block on + // purpose because preparing a transition might throw an exception (e.g. if we find multiple + // specs matching this transition), in which case we want to throw that exception here + // before even starting the transition. + prepareTransitionBeforeStarting(transition) + try { // Start the transition. startTransitionInternal(transition, chain) - // Handle transition links. - previousTransition?.let { cancelActiveTransitionLinks(it) } - if (stateLinks.isNotEmpty()) { - coroutineScope { setupTransitionLinks(transition) } - } - // Run the transition until it is finished. - transition.run() + transition.runInternal() } finally { finishTransition(transition) } } - private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) { + private fun prepareTransitionBeforeStarting(transition: TransitionState.Transition) { // Set the current scene and overlays on the transition. val currentState = transitionState transition.currentSceneWhenTransitionStarted = currentState.currentScene @@ -411,7 +402,9 @@ internal class MutableSceneTransitionLayoutStateImpl( } else { transition.updateOverscrollSpecs(fromSpec = null, toSpec = null) } + } + private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) { when (val currentState = transitionStates.last()) { is TransitionState.Idle -> { // Replace [Idle] by [transition]. @@ -471,42 +464,6 @@ internal class MutableSceneTransitionLayoutStateImpl( ) } - private fun cancelActiveTransitionLinks(transition: TransitionState.Transition) { - transition.activeTransitionLinks.forEach { (link, linkedTransition) -> - link.target.finishTransition(linkedTransition) - } - transition.activeTransitionLinks.clear() - } - - private fun CoroutineScope.setupTransitionLinks(transition: TransitionState.Transition) { - stateLinks.fastForEach { stateLink -> - val matchingLinks = - stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) } - if (matchingLinks.isEmpty()) return@fastForEach - if (matchingLinks.size > 1) error("More than one link matched.") - - val targetCurrentScene = stateLink.target.transitionState.currentScene - val matchingLink = matchingLinks[0] - - if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach - - val linkedTransition = - LinkedTransition( - originalTransition = transition, - fromScene = targetCurrentScene, - toScene = matchingLink.targetTo, - key = matchingLink.targetTransitionKey, - ) - - // Start with UNDISPATCHED so that startTransition is called directly and the new linked - // transition is observable directly. - launch(start = CoroutineStart.UNDISPATCHED) { - stateLink.target.startTransition(linkedTransition) - } - transition.activeTransitionLinks[stateLink] = linkedTransition - } - } - /** * Notify that [transition] was finished and that it settled to its * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was @@ -520,9 +477,9 @@ internal class MutableSceneTransitionLayoutStateImpl( return } - // Make sure that this transition settles in case it was force finished, for instance by - // calling snapToScene(). - transition.freezeAndAnimateToCurrentState() + // Make sure that this transition is cancelled in case it was force finished, for instance + // if snapToScene() is called. + transition.coroutineScope.cancel() val transitionStates = this.transitionStates if (!transitionStates.contains(transition)) { @@ -530,14 +487,12 @@ internal class MutableSceneTransitionLayoutStateImpl( return } + Log.i(TAG, "finishTransition(transition=$transition)") check(transitionStates.fastAll { it is TransitionState.Transition }) // Mark this transition as finished. finishedTransitions.add(transition) - // Finish all linked transitions. - finishActiveTransitionLinks(transition) - // Keep a reference to the last transition, in case we remove all transitions and should // settle to Idle. val lastTransition = transitionStates.last() @@ -560,13 +515,10 @@ internal class MutableSceneTransitionLayoutStateImpl( // If all transitions are finished, we are idle. if (i == nStates) { check(finishedTransitions.isEmpty()) - this.transitionStates = - listOf( - TransitionState.Idle( - lastTransition.currentScene, - lastTransition.currentOverlays, - ) - ) + val idle = + TransitionState.Idle(lastTransition.currentScene, lastTransition.currentOverlays) + Log.i(TAG, "all transitions finished. idle=$idle") + this.transitionStates = listOf(idle) } else if (i > 0) { this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } @@ -584,13 +536,6 @@ internal class MutableSceneTransitionLayoutStateImpl( transitionStates = listOf(TransitionState.Idle(scene, currentOverlays)) } - private fun finishActiveTransitionLinks(transition: TransitionState.Transition) { - for ((link, linkedTransition) in transition.activeTransitionLinks) { - link.target.finishTransition(linkedTransition) - } - transition.activeTransitionLinks.clear() - } - /** * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap * to the closest scene. @@ -614,8 +559,8 @@ internal class MutableSceneTransitionLayoutStateImpl( } val shouldSnap = - (isProgressCloseTo(0f) && transition.currentScene == transition.fromContent) || - (isProgressCloseTo(1f) && transition.currentScene == transition.toContent) + (isProgressCloseTo(0f) && transition.isFromCurrentContent()) || + (isProgressCloseTo(1f) && transition.isToCurrentContent()) return if (shouldSnap) { finishAllTransitions() true @@ -755,22 +700,23 @@ internal class MutableSceneTransitionLayoutStateImpl( animate() } - private fun transformationsWithElevation( + private fun transformationFactoriesWithElevation( transitionStates: List - ): List { + ): List { return buildList { transitionStates.fastForEach { state -> if (state !is TransitionState.Transition) { return@fastForEach } - state.transformationSpec.transformations.fastForEach { transformationWithRange -> - val transformation = transformationWithRange.transformation + state.transformationSpec.transformationMatchers.fastForEach { transformationMatcher + -> + val factory = transformationMatcher.factory if ( - transformation is SharedElementTransformation && - transformation.elevateInContent != null + factory is SharedElementTransformation.Factory && + factory.elevateInContent != null ) { - add(transformation) + add(factory) } } } @@ -785,10 +731,10 @@ internal class MutableSceneTransitionLayoutStateImpl( * necessary, for performance. */ internal fun isElevationPossible(content: ContentKey, element: ElementKey?): Boolean { - if (transformationsWithElevation.isEmpty()) return false - return transformationsWithElevation.fastAny { transformation -> - transformation.elevateInContent == content && - (element == null || transformation.matcher.matches(element, content)) + if (transformationFactoriesWithElevation.isEmpty()) return false + return transformationFactoriesWithElevation.fastAny { factory -> + factory.elevateInContent == content && + (element == null || factory.matcher.matches(element, content)) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index b083f79aebf515bf4b907a5a59309c741f169074..8df3f2d932b32a0ce485abeeaf8bede511231b38 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -26,18 +26,10 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach import com.android.compose.animation.scene.content.state.TransitionState -import com.android.compose.animation.scene.transformation.AnchoredSize -import com.android.compose.animation.scene.transformation.AnchoredTranslate -import com.android.compose.animation.scene.transformation.DrawScale -import com.android.compose.animation.scene.transformation.EdgeTranslate -import com.android.compose.animation.scene.transformation.Fade -import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation -import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationMatcher import com.android.compose.animation.scene.transformation.TransformationWithRange -import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ class SceneTransitions @@ -169,7 +161,7 @@ internal constructor( } /** The definition of a transition between [from] and [to]. */ -interface TransitionSpec { +internal interface TransitionSpec { /** The key of this [TransitionSpec]. */ val key: TransitionKey? @@ -209,7 +201,7 @@ interface TransitionSpec { fun previewTransformationSpec(transition: TransitionState.Transition): TransformationSpec? } -interface TransformationSpec { +internal interface TransformationSpec { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). @@ -232,8 +224,8 @@ interface TransformationSpec { */ val distance: UserActionDistance? - /** The list of [Transformation] applied to elements during this transition. */ - val transformations: List> + /** The list of [TransformationMatcher] applied to elements during this transformation. */ + val transformationMatchers: List companion object { internal val Empty = @@ -241,7 +233,7 @@ interface TransformationSpec { progressSpec = snap(), swipeSpec = null, distance = null, - transformations = emptyList(), + transformationMatchers = emptyList(), ) internal val EmptyProvider = { _: TransitionState.Transition -> Empty } } @@ -272,7 +264,14 @@ internal class TransitionSpecImpl( progressSpec = reverse.progressSpec, swipeSpec = reverse.swipeSpec, distance = reverse.distance, - transformations = reverse.transformations.map { it.reversed() }, + transformationMatchers = + reverse.transformationMatchers.map { + TransformationMatcher( + matcher = it.matcher, + factory = it.factory, + range = it.range?.reversed(), + ) + }, ) }, ) @@ -288,7 +287,7 @@ internal class TransitionSpecImpl( } /** The definition of the overscroll behavior of the [content]. */ -interface OverscrollSpec { +internal interface OverscrollSpec { /** The scene we are over scrolling. */ val content: ContentKey @@ -325,7 +324,7 @@ internal class TransformationSpecImpl( override val progressSpec: AnimationSpec, override val swipeSpec: SpringSpec?, override val distance: UserActionDistance?, - override val transformations: List>, + override val transformationMatchers: List, ) : TransformationSpec { private val cache = mutableMapOf>() @@ -335,7 +334,7 @@ internal class TransformationSpecImpl( .getOrPut(content) { computeTransformations(element, content) } } - /** Filter [transformations] to compute the [ElementTransformations] of [element]. */ + /** Filter [transformationMatchers] to compute the [ElementTransformations] of [element]. */ private fun computeTransformations( element: ElementKey, content: ContentKey, @@ -346,48 +345,56 @@ internal class TransformationSpecImpl( var drawScale: TransformationWithRange>? = null var alpha: TransformationWithRange>? = null - transformations.fastForEach { transformationWithRange -> - val transformation = transformationWithRange.transformation - if (!transformation.matcher.matches(element, content)) { + transformationMatchers.fastForEach { transformationMatcher -> + if (!transformationMatcher.matcher.matches(element, content)) { return@fastForEach } - when (transformation) { - is SharedElementTransformation -> { - throwIfNotNull(shared, element, name = "shared") - shared = - transformationWithRange - as TransformationWithRange + val transformation = transformationMatcher.factory.create() + val property = + when (transformation) { + is SharedElementTransformation -> { + throwIfNotNull(shared, element, name = "shared") + shared = + TransformationWithRange(transformation, transformationMatcher.range) + return@fastForEach + } + is PropertyTransformation<*> -> transformation.property } - is Translate, - is OverscrollTranslate, - is EdgeTranslate, - is AnchoredTranslate -> { + + when (property) { + is PropertyTransformation.Property.Offset -> { throwIfNotNull(offset, element, name = "offset") offset = - transformationWithRange - as TransformationWithRange> + TransformationWithRange( + transformation as PropertyTransformation, + transformationMatcher.range, + ) } - is ScaleSize, - is AnchoredSize -> { + is PropertyTransformation.Property.Size -> { throwIfNotNull(size, element, name = "size") size = - transformationWithRange - as TransformationWithRange> + TransformationWithRange( + transformation as PropertyTransformation, + transformationMatcher.range, + ) } - is DrawScale -> { + is PropertyTransformation.Property.Scale -> { throwIfNotNull(drawScale, element, name = "drawScale") drawScale = - transformationWithRange - as TransformationWithRange> + TransformationWithRange( + transformation as PropertyTransformation, + transformationMatcher.range, + ) } - is Fade -> { + is PropertyTransformation.Property.Alpha -> { throwIfNotNull(alpha, element, name = "alpha") alpha = - transformationWithRange - as TransformationWithRange> + TransformationWithRange( + transformation as PropertyTransformation, + transformationMatcher.range, + ) } - else -> error("Unknown transformation: $transformation") } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index f0043e1e89b0a3eb477b30a15e40cb5f1bfc6aa0..dbfeb5cd0168eaf92c5f97b384867009ef74b153 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import kotlin.math.absoluteValue @@ -66,8 +65,9 @@ internal fun createSwipeAnimation( val absoluteDistance = with(animation.contentTransition.transformationSpec.distance ?: DefaultSwipeDistance) { layoutImpl.userActionDistanceScope.absoluteDistance( - layoutImpl.content(animation.fromContent).targetSize, - orientation, + fromContent = animation.fromContent, + toContent = animation.toContent, + orientation = orientation, ) } @@ -475,12 +475,14 @@ internal class SwipeAnimation( private object DefaultSwipeDistance : UserActionDistance { override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, + fromContent: ContentKey, + toContent: ContentKey, orientation: Orientation, ): Float { + val fromContentSize = checkNotNull(fromContent.targetSize()) return when (orientation) { - Orientation.Horizontal -> fromSceneSize.width - Orientation.Vertical -> fromSceneSize.height + Orientation.Horizontal -> fromContentSize.width + Orientation.Vertical -> fromContentSize.height }.toFloat() } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index ba5f4144aff987c55157a832d9ec7e176e75b79a..5ab306a63a7e1021e836c3bc4b2cd6107caf81c8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -56,7 +56,7 @@ private fun DraggableHandlerImpl.contentForSwipes(): Content { } /** Whether swipe should be enabled in the given [orientation]. */ -private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { +internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { if (userActions.isEmpty()) { return false } @@ -165,8 +165,7 @@ private class SwipeToSceneNode( private val nestedScrollHandlerImpl = NestedScrollHandlerImpl( - layoutImpl = draggableHandler.layoutImpl, - orientation = draggableHandler.orientation, + draggableHandler = draggableHandler, topOrLeftBehavior = NestedScrollBehavior.Default, bottomOrRightBehavior = NestedScrollBehavior.Default, isExternalOverscrollGesture = { false }, @@ -200,10 +199,10 @@ private class SwipeToSceneNode( override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() - private fun startDragImmediately(pointersInfo: PointersInfo): Boolean { + private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean { // Immediately start the drag if the user can't swipe in the other direction and the gesture // handler can intercept it. - return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo) + return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown) } private fun canOppositeSwipe(): Boolean { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index dc26b6b382b4ad56f71054b2991322d8e665e2bb..48f08a7086d61752acd099ca032f66722d8b20ff 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.Transformation import kotlin.math.tanh /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */ @@ -75,7 +76,7 @@ interface SceneTransitionsBuilder { preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, - ): TransitionSpec + ) /** * Define the animation to be played when transitioning [from] the specified content. For the @@ -101,7 +102,7 @@ interface SceneTransitionsBuilder { preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, - ): TransitionSpec + ) /** * Define the animation to be played when the [content] is overscrolled in the given @@ -114,13 +115,13 @@ interface SceneTransitionsBuilder { content: ContentKey, orientation: Orientation, builder: OverscrollBuilder.() -> Unit, - ): OverscrollSpec + ) /** * Prevents overscroll the [content] in the given [orientation], allowing ancestors to * eventually consume the remaining gesture. */ - fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec + fun overscrollDisabled(content: ContentKey, orientation: Orientation) } interface BaseTransitionBuilder : PropertyTransformationBuilder { @@ -527,6 +528,9 @@ interface PropertyTransformationBuilder { anchorWidth: Boolean = true, anchorHeight: Boolean = true, ) + + /** Apply a [transformation] to the element(s) matching [matcher]. */ + fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) } /** This converter lets you change a linear progress into a function of your choice. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index e461f9ccc295e6bd3c75e58f2c98f448d6990034..6742b3200ac450495076635a8dba54e75116a709 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -37,8 +37,8 @@ import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationMatcher import com.android.compose.animation.scene.transformation.TransformationRange -import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -66,8 +66,8 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, builder: TransitionBuilder.() -> Unit, - ): TransitionSpec { - return transition(from = null, to = to, key = key, preview, reversePreview, builder) + ) { + transition(from = null, to = to, key = key, preview, reversePreview, builder) } override fun from( @@ -77,25 +77,25 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, builder: TransitionBuilder.() -> Unit, - ): TransitionSpec { - return transition(from = from, to = to, key = key, preview, reversePreview, builder) + ) { + transition(from = from, to = to, key = key, preview, reversePreview, builder) } override fun overscroll( content: ContentKey, orientation: Orientation, builder: OverscrollBuilder.() -> Unit, - ): OverscrollSpec { + ) { val impl = OverscrollBuilderImpl().apply(builder) - check(impl.transformations.isNotEmpty()) { + check(impl.transformationMatchers.isNotEmpty()) { "This method does not allow empty transformations. " + "Use overscrollDisabled($content, $orientation) instead." } - return overscrollSpec(content, orientation, impl) + overscrollSpec(content, orientation, impl) } - override fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec { - return overscrollSpec(content, orientation, OverscrollBuilderImpl()) + override fun overscrollDisabled(content: ContentKey, orientation: Orientation) { + overscrollSpec(content, orientation, OverscrollBuilderImpl()) } private fun overscrollSpec( @@ -112,7 +112,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { progressSpec = snap(), swipeSpec = null, distance = impl.distance, - transformations = impl.transformations, + transformationMatchers = impl.transformationMatchers, ), progressConverter = impl.progressConverter, ) @@ -137,7 +137,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { progressSpec = impl.spec, swipeSpec = impl.swipeSpec, distance = impl.distance, - transformations = impl.transformations, + transformationMatchers = impl.transformationMatchers, ) } @@ -157,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { - val transformations = mutableListOf>() + val transformationMatchers = mutableListOf() private var range: TransformationRange? = null protected var reversed = false override var distance: UserActionDistance? = null @@ -173,23 +173,31 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { range = null } - protected fun transformation(transformation: Transformation) { - val transformationWithRange = TransformationWithRange(transformation, range) - transformations.add( - if (reversed) { - transformationWithRange.reversed() - } else { - transformationWithRange - } + protected fun addTransformation( + matcher: ElementMatcher, + transformation: Transformation.Factory, + ) { + transformationMatchers.add( + TransformationMatcher( + matcher, + transformation, + range?.let { range -> + if (reversed) { + range.reversed() + } else { + range + } + }, + ) ) } override fun fade(matcher: ElementMatcher) { - transformation(Fade(matcher)) + addTransformation(matcher, Fade.Factory) } override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) { - transformation(Translate(matcher, x, y)) + addTransformation(matcher, Translate.Factory(x, y)) } override fun translate( @@ -197,19 +205,19 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { edge: Edge, startsOutsideLayoutBounds: Boolean, ) { - transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds)) + addTransformation(matcher, EdgeTranslate.Factory(edge, startsOutsideLayoutBounds)) } override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) { - transformation(AnchoredTranslate(matcher, anchor)) + addTransformation(matcher, AnchoredTranslate.Factory(anchor)) } override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) { - transformation(ScaleSize(matcher, width, height)) + addTransformation(matcher, ScaleSize.Factory(width, height)) } override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) { - transformation(DrawScale(matcher, scaleX, scaleY, pivot)) + addTransformation(matcher, DrawScale.Factory(scaleX, scaleY, pivot)) } override fun anchoredSize( @@ -218,7 +226,12 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { anchorWidth: Boolean, anchorHeight: Boolean, ) { - transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight)) + addTransformation(matcher, AnchoredSize.Factory(anchor, anchorWidth, anchorHeight)) + } + + override fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) { + check(range == null) { "Custom transformations can not be applied inside a range" } + addTransformation(matcher, transformation) } } @@ -257,7 +270,10 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr "(${transition.toContent.debugName})" } - transformation(SharedElementTransformation(matcher, enabled, elevateInContent)) + addTransformation( + matcher, + SharedElementTransformation.Factory(matcher, enabled, elevateInContent), + ) } override fun timestampRange( @@ -288,6 +304,6 @@ internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), Overscr x: OverscrollScope.() -> Float, y: OverscrollScope.() -> Float, ) { - transformation(OverscrollTranslate(matcher, x, y)) + addTransformation(matcher, OverscrollTranslate.Factory(x, y)) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 8187e393297537efe79302a017ea2b0924b4c9d2..255a16c6de6b7377715c39893ac308288e8ef79a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -16,7 +16,7 @@ package com.android.compose.animation.scene.content -import androidx.compose.foundation.gestures.Orientation +import android.annotation.SuppressLint import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable @@ -72,6 +72,7 @@ internal sealed class Content( var targetSize by mutableStateOf(IntSize.Zero) var userActions by mutableStateOf(actions) + @SuppressLint("NotConstructor") @Composable fun Content(modifier: Modifier = Modifier) { Box( @@ -151,8 +152,7 @@ internal class ContentScopeImpl( isExternalOverscrollGesture: () -> Boolean, ): Modifier { return nestedScrollToScene( - layoutImpl = layoutImpl, - orientation = Orientation.Horizontal, + draggableHandler = layoutImpl.horizontalDraggableHandler, topOrLeftBehavior = leftBehavior, bottomOrRightBehavior = rightBehavior, isExternalOverscrollGesture = isExternalOverscrollGesture, @@ -165,8 +165,7 @@ internal class ContentScopeImpl( isExternalOverscrollGesture: () -> Boolean, ): Modifier { return nestedScrollToScene( - layoutImpl = layoutImpl, - orientation = Orientation.Vertical, + draggableHandler = layoutImpl.verticalDraggableHandler, topOrLeftBehavior = topBehavior, bottomOrRightBehavior = bottomBehavior, isExternalOverscrollGesture = isExternalOverscrollGesture, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt index 7d29a68e3a8dddf719049d79f5835413b91e1ab6..d66fe42084de2d7da43d7437716974bce3f9e045 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt @@ -18,6 +18,7 @@ package com.android.compose.animation.scene.content.state import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable @@ -34,8 +35,8 @@ import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransformationSpec import com.android.compose.animation.scene.TransformationSpecImpl import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.transition.link.LinkedTransition -import com.android.compose.animation.scene.transition.link.StateLink +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch /** The state associated to a [SceneTransitionLayout] at some specific point in time. */ @@ -89,6 +90,10 @@ sealed interface TransitionState { // The set of overlays does not change in a [ChangeCurrentScene] transition. return currentOverlaysWhenTransitionStarted } + + override fun toString(): String { + return "ChangeScene(fromScene=$fromScene, toScene=$toScene)" + } } /** @@ -129,7 +134,7 @@ sealed interface TransitionState { * starting a swipe transition to show [overlay] and will be `true` only once the swipe * transition is committed. */ - protected abstract val isEffectivelyShown: Boolean + abstract val isEffectivelyShown: Boolean init { check( @@ -145,6 +150,12 @@ sealed interface TransitionState { currentOverlaysWhenTransitionStarted - overlay } } + + override fun toString(): String { + val isShowing = overlay == toContent + return "ShowOrHideOverlay(overlay=$overlay, fromOrToScene=$fromOrToScene, " + + "isShowing=$isShowing)" + } } /** We are transitioning from [fromOverlay] to [toOverlay]. */ @@ -164,7 +175,7 @@ sealed interface TransitionState { * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is * committed. */ - protected abstract val effectivelyShownOverlay: OverlayKey + abstract val effectivelyShownOverlay: OverlayKey init { check(fromOverlay != toOverlay) @@ -193,6 +204,10 @@ sealed interface TransitionState { add(include) } } + + override fun toString(): String { + return "ReplaceOverlay(fromOverlay=$fromOverlay, toOverlay=$toOverlay)" + } } /** @@ -280,8 +295,24 @@ sealed interface TransitionState { */ private var interruptionDecay: Animatable? = null - /** The map of active links that connects this transition to other transitions. */ - internal val activeTransitionLinks = mutableMapOf() + /** + * The coroutine scope associated to this transition. + * + * This coroutine scope can be used to launch animations associated to this transition, + * which will not finish until at least one animation/job is still running in the scope. + * + * Important: Make sure to never launch long-running jobs in this scope, otherwise the + * transition will never be considered as finished. + */ + internal val coroutineScope: CoroutineScope + get() = + _coroutineScope + ?: error( + "Transition.coroutineScope can only be accessed once the transition was " + + "started " + ) + + private var _coroutineScope: CoroutineScope? = null init { check(fromContent != toContent) @@ -327,8 +358,23 @@ sealed interface TransitionState { } } + /** Whether [fromContent] is effectively the current content of the transition. */ + internal fun isFromCurrentContent() = isCurrentContent(expectedFrom = true) + + /** Whether [toContent] is effectively the current content of the transition. */ + internal fun isToCurrentContent() = isCurrentContent(expectedFrom = false) + + private fun isCurrentContent(expectedFrom: Boolean): Boolean { + val expectedContent = if (expectedFrom) fromContent else toContent + return when (this) { + is ChangeScene -> currentScene == expectedContent + is ReplaceOverlay -> effectivelyShownOverlay == expectedContent + is ShowOrHideOverlay -> isEffectivelyShown == (expectedContent == overlay) + } + } + /** Run this transition and return once it is finished. */ - abstract suspend fun run() + protected abstract suspend fun run() /** * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will @@ -341,6 +387,14 @@ sealed interface TransitionState { */ abstract fun freezeAndAnimateToCurrentState() + internal suspend fun runInternal() { + check(_coroutineScope == null) { "A Transition can be started only once." } + coroutineScope { + _coroutineScope = this + run() + } + } + internal fun updateOverscrollSpecs( fromSpec: OverscrollSpecImpl?, toSpec: OverscrollSpecImpl?, @@ -361,7 +415,7 @@ sealed interface TransitionState { else -> null } ?: return true - return specForCurrentScene.transformationSpec.transformations.isNotEmpty() + return specForCurrentScene.transformationSpec.transformationMatchers.isNotEmpty() } internal open fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float { @@ -376,7 +430,7 @@ sealed interface TransitionState { val progressSpec = spring( stiffness = swipeSpec.stiffness, - dampingRatio = swipeSpec.dampingRatio, + dampingRatio = Spring.DampingRatioNoBouncy, visibilityThreshold = ProgressVisibilityThreshold, ) animatable.animateTo(0f, progressSpec) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt new file mode 100644 index 0000000000000000000000000000000000000000..bfb5ca733d90794fdfeb8b7256b1dd2877ee11b2 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.compose.animation.scene.reveal + +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.DeferredTargetAnimation +import androidx.compose.animation.core.ExperimentalAnimatableApi +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.spring +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.OverlayKey +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.UserActionDistance +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.CustomPropertyTransformation +import com.android.compose.animation.scene.transformation.PropertyTransformation +import com.android.compose.animation.scene.transformation.PropertyTransformationScope +import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineScope + +interface ContainerRevealHaptics { + /** + * Called when the reveal threshold is crossed while the user was dragging on screen. + * + * Important: This callback is called during layout and its implementation should therefore be + * very fast or posted to a different thread. + * + * @param revealed whether we go from hidden to revealed, i.e. whether the container size is + * going to jump from a smaller size to a bigger size. + */ + fun onRevealThresholdCrossed(revealed: Boolean) +} + +/** Animate the reveal of [container] by animating its size. */ +fun TransitionBuilder.verticalContainerReveal( + container: ElementKey, + haptics: ContainerRevealHaptics, +) { + // Make the swipe distance be exactly the target height of the container. + // TODO(b/376438969): Make sure that this works correctly when the target size of the element + // is changing during the transition (e.g. a notification was added). At the moment, the user + // action distance is only called until it returns a value > 0f, which is then cached. + distance = UserActionDistance { fromContent, toContent, _ -> + val targetSizeInFromContent = container.targetSize(fromContent) + val targetSizeInToContent = container.targetSize(toContent) + if (targetSizeInFromContent != null && targetSizeInToContent != null) { + error( + "verticalContainerReveal should not be used with shared elements, but " + + "${container.debugName} is in both ${fromContent.debugName} and " + + toContent.debugName + ) + } + + (targetSizeInToContent?.height ?: targetSizeInFromContent?.height)?.toFloat() ?: 0f + } + + // TODO(b/376438969): Improve the motion of this gesture using Motion Mechanics. + + // The min distance to swipe before triggering the reveal spring. + val distanceThreshold = 80.dp + + // The minimum height of the container. + val minHeight = 10.dp + + // The amount removed from the container width at 0% progress. + val widthDelta = 140.dp + + // The ratio at which the distance is tracked before reaching the threshold, e.g. if the user + // drags 60dp then the height will be 60dp * 0.25f = 15dp. + val trackingRatio = 0.25f + + // The max progress starting from which the container should always be visible, even if we are + // animating the container out. This is used so that we don't immediately fade out the container + // when triggering a one-off animation that hides it. + val alphaProgressThreshold = 0.05f + + // The spring animating the size of the container. + val sizeSpec = spring(stiffness = 380f, dampingRatio = 0.9f) + + // The spring animating the alpha of the container. + val alphaSpec = spring(stiffness = 1200f, dampingRatio = 0.99f) + + // The spring animating the progress when releasing the finger. + swipeSpec = + spring( + stiffness = Spring.StiffnessMediumLow, + dampingRatio = Spring.DampingRatioNoBouncy, + visibilityThreshold = 0.5f, + ) + + // Size transformation. + transformation(container) { + VerticalContainerRevealSizeTransformation( + haptics, + distanceThreshold, + trackingRatio, + minHeight, + widthDelta, + sizeSpec, + ) + } + + // Alpha transformation. + transformation(container) { + ContainerRevealAlphaTransformation(alphaSpec, alphaProgressThreshold) + } +} + +@OptIn(ExperimentalAnimatableApi::class) +private class VerticalContainerRevealSizeTransformation( + private val haptics: ContainerRevealHaptics, + private val distanceThreshold: Dp, + private val trackingRatio: Float, + private val minHeight: Dp, + private val widthDelta: Dp, + private val spec: FiniteAnimationSpec, +) : CustomPropertyTransformation { + override val property = PropertyTransformation.Property.Size + + private val widthAnimation = DeferredTargetAnimation(Float.VectorConverter) + private val heightAnimation = DeferredTargetAnimation(Float.VectorConverter) + + private var previousHasReachedThreshold: Boolean? = null + + override fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): IntSize { + // The distance to go to 100%. Note that we don't use + // TransitionState.HasOverscrollProperties.absoluteDistance because the transition will not + // implement HasOverscrollProperties if the transition is triggered and not gesture based. + val idleSize = checkNotNull(element.targetSize(content)) + val userActionDistance = idleSize.height + val progress = + when ((transition as? TransitionState.HasOverscrollProperties)?.bouncingContent) { + null -> transition.progressTo(content) + content -> 1f + else -> 0f + } + val distance = (progress * userActionDistance).fastCoerceAtLeast(0f) + val threshold = distanceThreshold.toPx() + + // Width. + val widthDelta = widthDelta.toPx() + val width = + (idleSize.width - widthDelta + + animateSize( + size = widthDelta, + distance = distance, + threshold = threshold, + transitionScope = transitionScope, + animation = widthAnimation, + )) + .roundToInt() + + // Height. + val minHeight = minHeight.toPx() + val height = + ( + // 1) The minimum size of the container. + minHeight + + + // 2) The animated size between the minimum size and the threshold. + animateSize( + size = threshold - minHeight, + distance = distance, + threshold = threshold, + transitionScope = transitionScope, + animation = heightAnimation, + ) + + + // 3) The remaining height after the threshold, tracking the finger. + (distance - threshold).fastCoerceAtLeast(0f)) + .roundToInt() + .fastCoerceAtMost(idleSize.height) + + // Haptics. + val hasReachedThreshold = distance >= threshold + if ( + previousHasReachedThreshold != null && + hasReachedThreshold != previousHasReachedThreshold && + transition.isUserInputOngoing + ) { + haptics.onRevealThresholdCrossed(revealed = hasReachedThreshold) + } + previousHasReachedThreshold = hasReachedThreshold + + return IntSize(width = width, height = height) + } + + /** + * Animate a size up to [size], so that it is equal to 0f when distance is 0f and equal to + * [size] when `distance >= threshold`, taking the [trackingRatio] into account. + */ + @OptIn(ExperimentalAnimatableApi::class) + private fun animateSize( + size: Float, + distance: Float, + threshold: Float, + transitionScope: CoroutineScope, + animation: DeferredTargetAnimation, + ): Float { + val trackingSize = distance.fastCoerceAtMost(threshold) / threshold * size * trackingRatio + val springTarget = + if (distance >= threshold) { + size * (1f - trackingRatio) + } else { + 0f + } + val springSize = animation.updateTarget(springTarget, transitionScope, spec) + return trackingSize + springSize + } +} + +@OptIn(ExperimentalAnimatableApi::class) +private class ContainerRevealAlphaTransformation( + private val spec: FiniteAnimationSpec, + private val progressThreshold: Float, +) : CustomPropertyTransformation { + override val property = PropertyTransformation.Property.Alpha + private val alphaAnimation = DeferredTargetAnimation(Float.VectorConverter) + + override fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): Float { + return alphaAnimation.updateTarget(targetAlpha(transition, content), transitionScope, spec) + } + + private fun targetAlpha(transition: TransitionState.Transition, content: ContentKey): Float { + if (transition.isUserInputOngoing) { + if (transition !is TransitionState.HasOverscrollProperties) { + error( + "Unsupported transition driven by user input but that does not have " + + "overscroll properties: $transition" + ) + } + + val bouncingContent = transition.bouncingContent + return if (bouncingContent != null) { + if (bouncingContent == content) 1f else 0f + } else { + if (transition.progressTo(content) > 0f) 1f else 0f + } + } + + // The transition was committed (the user released their finger), so the alpha depends on + // whether we are animating towards the content (showing the container) or away from it + // (hiding the container). + val isShowingContainer = + when (content) { + is SceneKey -> transition.currentScene == content + is OverlayKey -> transition.currentOverlays.contains(content) + } + + return if (isShowingContainer || transition.progressTo(content) >= progressThreshold) { + 1f + } else { + 0f + } + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 0ddeb7c7445f8c83598f7d89cfe5dc2e29d339f3..6575068201d885e63874d69bccfa0976371d1c4b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -19,16 +19,17 @@ package com.android.compose.animation.scene.transformation import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.content.state.TransitionState /** Anchor the size of an element to the size of another element. */ -internal class AnchoredSize( - override val matcher: ElementMatcher, +internal class AnchoredSize +private constructor( private val anchor: ElementKey, private val anchorWidth: Boolean, private val anchorHeight: Boolean, -) : PropertyTransformation { +) : InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Size + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -59,4 +60,12 @@ internal class AnchoredSize( anchorSizeIn(transition.fromContent) } } + + class Factory( + private val anchor: ElementKey, + private val anchorWidth: Boolean, + private val anchorHeight: Boolean, + ) : Transformation.Factory { + override fun create(): Transformation = AnchoredSize(anchor, anchorWidth, anchorHeight) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index 47508b41633c06b02d464d3bb600f1314fbc96a2..890902b7ab67735719ae0dd2fc59ef8636b3984e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt @@ -19,14 +19,13 @@ package com.android.compose.animation.scene.transformation import androidx.compose.ui.geometry.Offset import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.content.state.TransitionState /** Anchor the translation of an element to another element. */ -internal class AnchoredTranslate( - override val matcher: ElementMatcher, - private val anchor: ElementKey, -) : PropertyTransformation { +internal class AnchoredTranslate private constructor(private val anchor: ElementKey) : + InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Offset + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -56,6 +55,10 @@ internal class AnchoredTranslate( Offset(idleValue.x + offset.x, idleValue.y + offset.y) } } + + class Factory(private val anchor: ElementKey) : Transformation.Factory { + override fun create(): Transformation = AnchoredTranslate(anchor) + } } internal fun throwMissingAnchorException( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt index 8488ae5178b04fcae05fd6a5855d96663b0e4e0d..347f1c325058067d06fde15066c3efa3bcdc24e2 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -19,7 +19,6 @@ package com.android.compose.animation.scene.transformation import androidx.compose.ui.geometry.Offset import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.content.state.TransitionState @@ -27,12 +26,14 @@ import com.android.compose.animation.scene.content.state.TransitionState * Scales the draw size of an element. Note this will only scale the draw inside of an element, * therefore it won't impact layout of elements around it. */ -internal class DrawScale( - override val matcher: ElementMatcher, +internal class DrawScale +private constructor( private val scaleX: Float, private val scaleY: Float, - private val pivot: Offset = Offset.Unspecified, -) : PropertyTransformation { + private val pivot: Offset, +) : InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Scale + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -41,4 +42,9 @@ internal class DrawScale( ): Scale { return Scale(scaleX, scaleY, pivot) } + + class Factory(private val scaleX: Float, private val scaleY: Float, private val pivot: Offset) : + Transformation.Factory { + override fun create(): Transformation = DrawScale(scaleX, scaleY, pivot) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 884aae4b8b1aae6e462abcc777b24dd7d713a867..f8e6dc09ce75c9ec33473e936e003ff15301f793 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -20,15 +20,14 @@ import androidx.compose.ui.geometry.Offset import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.content.state.TransitionState /** Translate an element from an edge of the layout. */ -internal class EdgeTranslate( - override val matcher: ElementMatcher, - private val edge: Edge, - private val startsOutsideLayoutBounds: Boolean = true, -) : PropertyTransformation { +internal class EdgeTranslate +private constructor(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) : + InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Offset + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -67,4 +66,9 @@ internal class EdgeTranslate( } } } + + class Factory(private val edge: Edge, private val startsOutsideLayoutBounds: Boolean) : + Transformation.Factory { + override fun create(): Transformation = EdgeTranslate(edge, startsOutsideLayoutBounds) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index ef769e7d0c19b34dff864032f0f577554a01d2d5..d92419ef368df45ee660aefcbc9ffa9467b87af4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt @@ -18,11 +18,12 @@ package com.android.compose.animation.scene.transformation import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.content.state.TransitionState /** Fade an element in or out. */ -internal class Fade(override val matcher: ElementMatcher) : PropertyTransformation { +internal object Fade : InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Alpha + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -33,4 +34,8 @@ internal class Fade(override val matcher: ElementMatcher) : PropertyTransformati // fading out, which is `0` in both cases. return 0f } + + object Factory : Transformation.Factory { + override fun create(): Transformation = Fade + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index ef3654b65b0a5838ca109188b6a927b93ab15fcc..5d31fd9ca1961327673821bccfb31f9156a514e0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt @@ -19,7 +19,6 @@ package com.android.compose.animation.scene.transformation import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.roundToInt @@ -27,11 +26,10 @@ import kotlin.math.roundToInt * Scales the size of an element. Note that this makes the element resize every frame and will * therefore impact the layout of other elements. */ -internal class ScaleSize( - override val matcher: ElementMatcher, - private val width: Float = 1f, - private val height: Float = 1f, -) : PropertyTransformation { +internal class ScaleSize private constructor(private val width: Float, private val height: Float) : + InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Size + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -43,4 +41,9 @@ internal class ScaleSize( height = (idleValue.height * height).roundToInt(), ) } + + class Factory(private val width: Float = 1f, private val height: Float = 1f) : + Transformation.Factory { + override fun create(): Transformation = ScaleSize(width, height) + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 74a3ead3fbd707df9dea0d39234fa0bd28a12963..e0b42189854a395696660e8a5191e04a2f7a3bc5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -18,7 +18,9 @@ package com.android.compose.animation.scene.transformation import androidx.compose.animation.core.Easing import androidx.compose.animation.core.LinearEasing +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.util.fastCoerceAtLeast import androidx.compose.ui.util.fastCoerceAtMost @@ -27,30 +29,65 @@ import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.ElementStateScope +import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.content.state.TransitionState +import kotlinx.coroutines.CoroutineScope /** A transformation applied to one or more elements during a transition. */ sealed interface Transformation { - /** - * The matcher that should match the element(s) to which this transformation should be applied. - */ - val matcher: ElementMatcher - - /* - * Reverse this transformation. This is called when we use Transition(from = A, to = B) when - * animating from B to A and there is no Transition(from = B, to = A) defined. - */ - fun reversed(): Transformation = this + fun interface Factory { + fun create(): Transformation + } } -internal class SharedElementTransformation( - override val matcher: ElementMatcher, +// Important: SharedElementTransformation must be a data class because we check that we don't +// provide 2 different transformations for the same element in Element.kt +internal data class SharedElementTransformation( internal val enabled: Boolean, internal val elevateInContent: ContentKey?, -) : Transformation +) : Transformation { + class Factory( + internal val matcher: ElementMatcher, + internal val enabled: Boolean, + internal val elevateInContent: ContentKey?, + ) : Transformation.Factory { + override fun create(): Transformation { + return SharedElementTransformation(enabled, elevateInContent) + } + } +} + +/** + * A transformation that changes the value of an element [Property], like its [size][Property.Size] + * or [offset][Property.Offset]. + */ +sealed interface PropertyTransformation : Transformation { + /** The property to which this transformation is applied. */ + val property: Property + + sealed class Property { + /** The size of an element. */ + data object Size : Property() + + /** The offset (position) of an element. */ + data object Offset : Property() + + /** The alpha of an element. */ + data object Alpha : Property() + + /** + * The drawing scale of an element. Animating the scale does not have any effect on the + * layout. + */ + data object Scale : Property() + } +} -/** A transformation that changes the value of an element property, like its size or offset. */ -interface PropertyTransformation : Transformation { +/** + * A transformation to a target/transformed value that is automatically interpolated using the + * transition progress and transformation range. + */ +interface InterpolatedPropertyTransformation : PropertyTransformation { /** * Return the transformed value for the given property, i.e.: * - the value at progress = 0% for elements that are entering the layout (i.e. elements in the @@ -58,8 +95,8 @@ interface PropertyTransformation : Transformation { * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the * content we are transitioning from). * - * The returned value will be interpolated using the [transition] progress and [idleValue], the - * value of the property when we are idle. + * The returned value will be automatically interpolated using the [transition] progress, the + * transformation range and [idleValue], the value of the property when we are idle. */ fun PropertyTransformationScope.transform( content: ContentKey, @@ -69,13 +106,40 @@ interface PropertyTransformation : Transformation { ): T } +interface CustomPropertyTransformation : PropertyTransformation { + /** + * Return the value that the property should have in the current frame for the given [content] + * and [element]. + * + * This transformation can use [transitionScope] to launch animations associated to + * [transition], which will not finish until at least one animation/job is still running in the + * scope. + * + * Important: Make sure to never launch long-running jobs in [transitionScope], otherwise + * [transition] will never be considered as finished. + */ + fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): T +} + interface PropertyTransformationScope : Density, ElementStateScope { /** The current [direction][LayoutDirection] of the layout. */ val layoutDirection: LayoutDirection } +/** Defines the transformation-type to be applied to all elements matching [matcher]. */ +internal class TransformationMatcher( + val matcher: ElementMatcher, + val factory: Transformation.Factory, + val range: TransformationRange?, +) + /** A pair consisting of a [transformation] and optional [range]. */ -class TransformationWithRange( +internal data class TransformationWithRange( val transformation: T, val range: TransformationRange?, ) { @@ -87,7 +151,7 @@ class TransformationWithRange( } /** The progress-based range of a [PropertyTransformation]. */ -data class TransformationRange(val start: Float, val end: Float, val easing: Easing) { +internal data class TransformationRange(val start: Float, val end: Float, val easing: Easing) { constructor( start: Float? = null, end: Float? = null, @@ -101,7 +165,7 @@ data class TransformationRange(val start: Float, val end: Float, val easing: Eas } /** Reverse this range. */ - fun reversed() = + internal fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start), easing = easing) /** Get the progress of this range given the global [transitionProgress]. */ @@ -128,6 +192,6 @@ data class TransformationRange(val start: Float, val end: Float, val easing: Eas } companion object { - const val BoundUnspecified = Float.MIN_VALUE + internal const val BoundUnspecified = Float.MIN_VALUE } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index 356ed9969458eeb459320ee915718d30eaa83935..2f4d5bff8b413a368e1a96c20bc4fcfbb3e6944b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -19,18 +19,15 @@ package com.android.compose.animation.scene.transformation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.OverscrollScope import com.android.compose.animation.scene.content.state.TransitionState -internal class Translate( - override val matcher: ElementMatcher, - private val x: Dp = 0.dp, - private val y: Dp = 0.dp, -) : PropertyTransformation { +internal class Translate private constructor(private val x: Dp, private val y: Dp) : + InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Offset + override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, @@ -39,13 +36,19 @@ internal class Translate( ): Offset { return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx()) } + + class Factory(private val x: Dp, private val y: Dp) : Transformation.Factory { + override fun create(): Transformation = Translate(x, y) + } } -internal class OverscrollTranslate( - override val matcher: ElementMatcher, - val x: OverscrollScope.() -> Float = { 0f }, - val y: OverscrollScope.() -> Float = { 0f }, -) : PropertyTransformation { +internal class OverscrollTranslate +private constructor( + private val x: OverscrollScope.() -> Float, + private val y: OverscrollScope.() -> Float, +) : InterpolatedPropertyTransformation { + override val property = PropertyTransformation.Property.Offset + private val cachedOverscrollScope = CachedOverscrollScope() override fun PropertyTransformationScope.transform( @@ -63,6 +66,13 @@ internal class OverscrollTranslate( return Offset(x = value.x + overscrollScope.x(), y = value.y + overscrollScope.y()) } + + class Factory( + private val x: OverscrollScope.() -> Float, + private val y: OverscrollScope.() -> Float, + ) : Transformation.Factory { + override fun create(): Transformation = OverscrollTranslate(x, y) + } } /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt deleted file mode 100644 index 42ba9ba95e076ab5326eaa56c0fbf1d5628ef9eb..0000000000000000000000000000000000000000 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.compose.animation.scene.transition.link - -import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.content.state.TransitionState - -/** A linked transition which is driven by a [originalTransition]. */ -internal class LinkedTransition( - private val originalTransition: TransitionState.Transition, - fromScene: SceneKey, - toScene: SceneKey, - override val key: TransitionKey? = null, -) : TransitionState.Transition.ChangeScene(fromScene, toScene) { - - override val currentScene: SceneKey - get() { - return when (originalTransition.currentScene) { - originalTransition.fromContent -> fromScene - originalTransition.toContent -> toScene - else -> error("Original currentScene is neither FromScene nor ToScene") - } - } - - override val isInitiatedByUserInput: Boolean - get() = originalTransition.isInitiatedByUserInput - - override val isUserInputOngoing: Boolean - get() = originalTransition.isUserInputOngoing - - override val progress: Float - get() = originalTransition.progress - - override val progressVelocity: Float - get() = originalTransition.progressVelocity - - override suspend fun run() { - originalTransition.run() - } - - override fun freezeAndAnimateToCurrentState() { - originalTransition.freezeAndAnimateToCurrentState() - } -} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt deleted file mode 100644 index 2aec5091c3e5568417e7afc1343cb99d04d8c6ac..0000000000000000000000000000000000000000 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.compose.animation.scene.transition.link - -import com.android.compose.animation.scene.ContentKey -import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl -import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SceneTransitionLayoutState -import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.content.state.TransitionState - -/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */ -class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List) { - - internal val target = target as MutableSceneTransitionLayoutStateImpl - - /** - * Links two transitions (source and target) together. - * - * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`, - * `null`, `SceneA` means that any transition at the source will trigger a transition in the - * target to `SceneA` from any current scene. - */ - class TransitionLink( - val sourceFrom: ContentKey?, - val sourceTo: ContentKey?, - val targetFrom: SceneKey?, - val targetTo: SceneKey, - val targetTransitionKey: TransitionKey? = null, - ) { - init { - if ( - (sourceFrom != null && sourceFrom == sourceTo) || - (targetFrom != null && targetFrom == targetTo) - ) - error("From and To can't be the same") - } - - internal fun isMatchingLink(transition: TransitionState.Transition): Boolean { - return (sourceFrom == null || sourceFrom == transition.fromContent) && - (sourceTo == null || sourceTo == transition.toContent) - } - - internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean { - return (targetFrom == null || targetFrom == targetCurrentContent) && - targetTo != targetCurrentContent - } - } -} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index 20a0b390a0373c334121baf9e504e697ba584f99..3f182363e20cdec8a684bfb6759f8e01db2bd89a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -365,12 +365,16 @@ private class OnStopScopeImpl(private val controller: ScrollController) : OnStop flingBehavior: FlingBehavior, ): Float { return with(flingBehavior) { - object : ScrollScope { - override fun scrollBy(pixels: Float): Float { - return controller.onScroll(pixels, NestedScrollSource.SideEffect) + val remainingVelocity = + object : ScrollScope { + override fun scrollBy(pixels: Float): Float { + return controller.onScroll(pixels, NestedScrollSource.SideEffect) + } } - } - .performFling(initialVelocity) + .performFling(initialVelocity) + + // returns the consumed velocity + initialVelocity - remainingVelocity } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 098673e5d18614d1adfbd5423f370c9ec593743c..10057b280d28e8494c946621d0edc530baba73e3 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -52,17 +52,15 @@ import org.junit.runner.RunWith private const val SCREEN_SIZE = 100f private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) -private fun pointersInfo( +private fun pointersDown( startedPosition: Offset = Offset.Zero, pointersDown: Int = 1, - isMouseWheel: Boolean = false, pointersDownByType: Map = mapOf(PointerType.Touch to pointersDown), -): PointersInfo { - return PointersInfo( +): PointersInfo.PointersDown { + return PointersInfo.PointersDown( startedPosition = startedPosition, - pointersDown = pointersDown, - isMouseWheel = isMouseWheel, - pointersDownByType = pointersDownByType, + count = pointersDown, + countByType = pointersDownByType, ) } @@ -135,18 +133,17 @@ class DraggableHandlerTest { ) .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) } - val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) - val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) + val draggableHandler = layoutImpl.verticalDraggableHandler + val horizontalDraggableHandler = layoutImpl.horizontalDraggableHandler - var pointerInfoOwner: () -> PointersInfo = { pointersInfo() } + var pointerInfoOwner: () -> PointersInfo = { pointersDown() } fun nestedScrollConnection( nestedScrollBehavior: NestedScrollBehavior, isExternalOverscrollGesture: Boolean = false, ) = NestedScrollHandlerImpl( - layoutImpl = layoutImpl, - orientation = draggableHandler.orientation, + draggableHandler = draggableHandler, topOrLeftBehavior = nestedScrollBehavior, bottomOrRightBehavior = nestedScrollBehavior, isExternalOverscrollGesture = { isExternalOverscrollGesture }, @@ -221,7 +218,7 @@ class DraggableHandlerTest { } fun onDragStarted( - pointersInfo: PointersInfo = pointersInfo(), + pointersInfo: PointersInfo.PointersDown = pointersDown(), overSlop: Float, expectedConsumedOverSlop: Float = overSlop, ): DragController { @@ -235,18 +232,20 @@ class DraggableHandlerTest { ) } - fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController { + fun onDragStartedImmediately( + pointersInfo: PointersInfo.PointersDown = pointersDown() + ): DragController { return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f) } fun onDragStarted( draggableHandler: DraggableHandler, - pointersInfo: PointersInfo = pointersInfo(), + pointersInfo: PointersInfo.PointersDown = pointersDown(), overSlop: Float = 0f, expectedConsumedOverSlop: Float = overSlop, ): DragController { val dragController = - draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop) + draggableHandler.onDragStarted(pointersDown = pointersInfo, overSlop = overSlop) // MultiPointerDraggable will always call onDelta with the initial overSlop right after dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop) @@ -529,7 +528,7 @@ class DraggableHandlerTest { val dragController = onDragStarted( pointersInfo = - pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)), + pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)), overSlop = up(fractionOfScreen = 0.2f), ) assertTransition( @@ -555,7 +554,7 @@ class DraggableHandlerTest { // Start dragging from the bottom onDragStarted( - pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)), + pointersInfo = pointersDown(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)), overSlop = up(fractionOfScreen = 0.1f), ) assertTransition( @@ -645,7 +644,7 @@ class DraggableHandlerTest { mutableUserActionsA = emptyMap() mutableUserActionsB = emptyMap() - // start accelaratedScroll and scroll over to B -> null + // start acceleratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f) dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f) @@ -1052,7 +1051,7 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition( currentScene = SceneC, @@ -1084,7 +1083,7 @@ class DraggableHandlerTest { // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of // C leads to scene A (and not B), the previous transitions is *not* intercepted and we // instead animate from C to A. - val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)) + val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)) assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse() onDragStarted(pointersInfo = bottom, overSlop = up(0.1f)) @@ -1103,7 +1102,7 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true) @@ -1120,15 +1119,15 @@ class DraggableHandlerTest { @Test fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest { - assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse() onDragStarted(overSlop = up(0.1f)) - assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue() layoutState.startTransitionImmediately( animationScope = testScope.backgroundScope, transition(SceneA, SceneB), ) - assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse() } @Test @@ -1160,7 +1159,7 @@ class DraggableHandlerTest { assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) // Intercept the transition and swipe down back to scene A. - assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue() val dragController2 = onDragStartedImmediately() // Block the transition when the user release their finger. @@ -1204,7 +1203,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { pointersInfo() } + pointerInfoOwner = { pointersDown() } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1221,7 +1220,7 @@ class DraggableHandlerTest { advanceUntilIdle() // Drag from the **bottom** of the screen - pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) } + pointerInfoOwner = { pointersDown(startedPosition = Offset(0f, SCREEN_SIZE)) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1241,7 +1240,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Use mouse wheel - pointerInfoOwner = { pointersInfo(isMouseWheel = true) } + pointerInfoOwner = { PointersInfo.MouseWheel } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1251,7 +1250,7 @@ class DraggableHandlerTest { @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true) @@ -1265,7 +1264,7 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted( pointersInfo = middle, @@ -1296,7 +1295,7 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f) @@ -1342,7 +1341,7 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f)) val transition = assertThat(transitionState).isSceneTransition() @@ -1374,7 +1373,7 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f)) val transition = assertThat(transitionState).isSceneTransition() @@ -1405,7 +1404,7 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f)) val transition = assertThat(transitionState).isSceneTransition() @@ -1437,7 +1436,7 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f)) val transition = assertThat(transitionState).isSceneTransition() @@ -1471,7 +1470,7 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB)) - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) @@ -1504,7 +1503,7 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC)) - val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) @@ -1531,7 +1530,7 @@ class DraggableHandlerTest { fun interceptingTransitionKeepsDistance() = runGestureTest { var swipeDistance = 75f layoutState.transitions = transitions { - from(SceneA, to = SceneB) { distance = UserActionDistance { _, _ -> swipeDistance } } + from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } } } // Start transition. @@ -1676,4 +1675,33 @@ class DraggableHandlerTest { assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) } + + @Test + fun replaceOverlayNestedScroll() = runGestureTest { + layoutState.showOverlay(OverlayA, animationScope = testScope) + advanceUntilIdle() + + // Initial state. + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) + assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayA) + + // Swipe down to replace overlay A by overlay B. + + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) + nestedScroll.scroll(downOffset(0.1f)) + val transition = assertThat(layoutState.transitionState).isReplaceOverlayTransition() + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasFromOverlay(OverlayA) + assertThat(transition).hasToOverlay(OverlayB) + assertThat(transition).hasCurrentOverlays(OverlayA) + assertThat(transition).hasProgress(0.1f) + + nestedScroll.preFling(Velocity(0f, velocityThreshold)) + advanceUntilIdle() + // Commit the gesture. The overlays are instantly swapped in the set of current overlays. + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) + assertThat(layoutState.transitionState).hasCurrentOverlays(OverlayB) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 09b59394724d99c0c2c191f9ab0013bd00c3834b..b4c8ad7c3327fb35dcca5b0ad36a6080778ad208 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onAllNodesWithText @@ -404,4 +405,40 @@ class MovableElementTest { rule.waitForIdle() rule.onNodeWithTag(fooParentInOverlayTag).assertSizeIsEqualTo(fooSize) } + + @Test + fun movableElementInOverlayShouldBeComposed() { + val fooKey = MovableElementKey("foo", contents = setOf(OverlayA)) + val fooContentTag = "fooContentTag" + + @Composable + fun ContentScope.MovableFoo(modifier: Modifier = Modifier) { + MovableElement(fooKey, modifier) { + content { Box(Modifier.testTag(fooContentTag).size(100.dp)) } + } + } + + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + initialOverlays = setOf(OverlayA), + ) + } + + val scope = + rule.setContentAndCreateMainScope { + SceneTransitionLayout(state) { + scene(SceneA) { Box(Modifier.fillMaxSize()) } + overlay(OverlayA) { MovableFoo() } + overlay(OverlayB) { Box(Modifier.size(50.dp)) } + } + } + + rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp) + + // Show overlay B. This shouldn't have any impact on Foo that should still be composed in A. + scope.launch { state.startTransition(transition(SceneA, OverlayB)) } + rule.onNode(hasTestTag(fooContentTag)).assertIsDisplayed().assertSizeIsEqualTo(100.dp) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index a2b263b9f9edf908d1159c141be058be28766a24..79ca891babd137e6e15a2248864c9a82b1cb9704 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -23,13 +23,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC -import com.android.compose.animation.scene.TestScenes.SceneD import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.subjects.assertThat -import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.animation.scene.transition.seekToScene import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.TestSceneTransition @@ -37,11 +36,15 @@ import com.android.compose.test.runMonotonicClockTest import com.android.compose.test.transition import com.google.common.truth.Truth.assertThat import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Rule @@ -132,147 +135,6 @@ class SceneTransitionLayoutStateTest { assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } - private fun setupLinkedStates( - parentInitialScene: SceneKey = SceneC, - childInitialScene: SceneKey = SceneA, - sourceFrom: SceneKey? = SceneA, - sourceTo: SceneKey? = SceneB, - targetFrom: SceneKey? = SceneC, - targetTo: SceneKey = SceneD, - ): Pair { - val parentState = MutableSceneTransitionLayoutState(parentInitialScene) - val link = - listOf( - StateLink( - parentState, - listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo)), - ) - ) - val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link) - return Pair( - parentState as MutableSceneTransitionLayoutStateImpl, - childState as MutableSceneTransitionLayoutStateImpl, - ) - } - - @Test - fun linkedTransition_startsLinkAndFinishesLinkInToState() = runTest { - val (parentState, childState) = setupLinkedStates() - - val childTransition = transition(SceneA, SceneB) - - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() - assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) - } - - @Test - fun linkedTransition_transitiveLink() = runTest { - val parentParentState = - MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl - val parentLink = - listOf( - StateLink( - parentParentState, - listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC)), - ) - ) - val parentState = - MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink) - as MutableSceneTransitionLayoutStateImpl - val link = - listOf( - StateLink( - parentState, - listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD)), - ) - ) - val childState = - MutableSceneTransitionLayoutState(SceneA, stateLinks = link) - as MutableSceneTransitionLayoutStateImpl - - val childTransition = transition(SceneA, SceneB) - - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() - assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue() - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) - assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - } - - @Test - fun linkedTransition_linkProgressIsEqual() = runTest { - val (parentState, childState) = setupLinkedStates() - - var progress = 0f - val childTransition = transition(SceneA, SceneB, progress = { progress }) - - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(parentState.currentTransition?.progress).isEqualTo(0f) - - progress = .5f - assertThat(parentState.currentTransition?.progress).isEqualTo(.5f) - } - - @Test - fun linkedTransition_reverseTransitionIsNotLinked() = runTest { - val (parentState, childState) = setupLinkedStates() - - val childTransition = transition(SceneB, SceneA, current = { SceneB }) - - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue() - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - } - - @Test - fun linkedTransition_startsLinkAndFinishesLinkInFromState() = runTest { - val (parentState, childState) = setupLinkedStates() - - val childTransition = transition(SceneA, SceneB, current = { SceneA }) - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - } - - @Test - fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest { - val (parentState, childState) = setupLinkedStates() - - val childTransition = transition(SceneA, SceneB) - val parentTransition = transition(SceneC, SceneA) - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - parentState.startTransitionImmediately(animationScope = backgroundScope, parentTransition) - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) - assertThat(parentState.transitionState).isEqualTo(parentTransition) - } - @Test fun setTargetScene_withTransitionKey() = runMonotonicClockTest { val transitionkey = TransitionKey(debugName = "foo") @@ -291,7 +153,7 @@ class SceneTransitionLayoutStateTest { // Default transition from A to B. assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull() - assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1) + assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1) // Go back to A. state.setTargetScene(SceneA, animationScope = this) @@ -304,7 +166,7 @@ class SceneTransitionLayoutStateTest { state.setTargetScene(SceneB, animationScope = this, transitionKey = transitionkey) ) .isNotNull() - assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2) + assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2) } @Test @@ -326,6 +188,25 @@ class SceneTransitionLayoutStateTest { assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) } + @Test + fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + state.startTransitionImmediately( + animationScope = backgroundScope, + transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }), + ) + assertThat(state.isTransitioning()).isTrue() + + // Ignore the request if the progress is not close to 0 or 1, using the threshold. + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + + // Go to the initial scene if it is close to 0. + assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() + assertThat(state.isTransitioning()).isFalse() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA)) + } + @Test fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) @@ -345,6 +226,25 @@ class SceneTransitionLayoutStateTest { assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) } + @Test + fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + state.startTransitionImmediately( + animationScope = backgroundScope, + transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }), + ) + assertThat(state.isTransitioning()).isTrue() + + // Ignore the request if the progress is not close to 0 or 1, using the threshold. + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.isTransitioning()).isTrue() + + // Go to the final scene if it is close to 1. + assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() + assertThat(state.isTransitioning()).isFalse() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA))) + } + @Test fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) @@ -393,51 +293,6 @@ class SceneTransitionLayoutStateTest { assertThat(state.isTransitioning()).isTrue() } - @Test - fun linkedTransition_fuzzyLinksAreMatchedAndStarted() = runTest { - val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD) - val childTransition = transition(SceneA, SceneB) - - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() - assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD)) - } - - @Test - fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() = runTest { - val (parentState, childState) = - setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD) - - val childTransition = transition(SceneA, SceneB, current = { SceneA }) - - val job = - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() - assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue() - - childTransition.finish() - job.join() - assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA)) - assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC)) - } - - @Test - fun linkedTransition_fuzzyLinksAreNotMatched() = runTest { - val (parentState, childState) = - setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD) - val childTransition = transition(SceneA, SceneB) - - childState.startTransitionImmediately(animationScope = backgroundScope, childTransition) - assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue() - assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse() - } - private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB( progress: () -> Float, sceneTransitions: SceneTransitions, @@ -777,4 +632,84 @@ class SceneTransitionLayoutStateTest { assertThat(transition.progressTo(SceneA)).isEqualTo(1f - 0.2f) assertThrows(IllegalArgumentException::class.java) { transition.progressTo(SceneC) } } + + @Test + fun transitionCanBeStartedOnlyOnce() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + val transition = transition(from = SceneA, to = SceneB) + + state.startTransitionImmediately(backgroundScope, transition) + assertThrows(IllegalStateException::class.java) { + runBlocking { state.startTransition(transition) } + } + } + + @Test + fun transitionFinishedWhenScopeIsEmpty() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + // Start a transition. + val transition = transition(from = SceneA, to = SceneB) + state.startTransitionImmediately(backgroundScope, transition) + assertThat(state.transitionState).isSceneTransition() + + // Start a job in the transition scope. + val jobCompletable = CompletableDeferred() + transition.coroutineScope.launch { jobCompletable.await() } + + // Finish the transition (i.e. make its #run() method return). The transition should not be + // considered as finished yet given that there is a job still running in its scope. + transition.finish() + runCurrent() + assertThat(state.transitionState).isSceneTransition() + + // Finish the job in the scope. Now the transition should be considered as finished. + jobCompletable.complete(Unit) + runCurrent() + assertThat(state.transitionState).isIdle() + } + + @Test + fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + // Start a transition. + val transition = transition(from = SceneA, to = SceneB) + state.startTransitionImmediately(backgroundScope, transition) + assertThat(state.transitionState).isSceneTransition() + + // Start a job in the transition scope that never finishes. + val job = transition.coroutineScope.launch { awaitCancellation() } + + // Force snap state to SceneB to force finish all current transitions. + state.snapToScene(SceneB) + assertThat(state.transitionState).isIdle() + assertThat(job.isCancelled).isTrue() + } + + @Test + fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() { + val state = + MutableSceneTransitionLayoutState( + SceneA, + transitions { + // This transition definition is bad because they both match when transitioning + // from A to B. + from(SceneA) {} + to(SceneB) {} + }, + ) + + val exception = + assertThrows(IllegalStateException::class.java) { + runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) } + } + + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Found multiple transition specs for transition SceneKey(debugName=SceneA) => " + + "SceneKey(debugName=SceneB)" + ) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 3b2ee98a2a931b7253dd48fd335d7ad70e1c0c8f..b3a3261122a8c8fad90a02b8b60a8a717c66a905 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.test.swipeRight import androidx.compose.ui.test.swipeUp import androidx.compose.ui.test.swipeWithVelocity import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -574,7 +573,7 @@ class SwipeToSceneTest { rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { - scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + scene(SceneA, userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneB)) { Box( Modifier.fillMaxSize() // A scrollable that does not consume the scroll gesture @@ -672,12 +671,12 @@ class SwipeToSceneTest { } assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue() - assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1) + assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(1) // Move the pointer up to swipe to scene B using the new transition. rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) } assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue() - assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2) + assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2) } @Test @@ -685,7 +684,8 @@ class SwipeToSceneTest { val swipeDistance = object : UserActionDistance { override fun UserActionDistanceScope.absoluteDistance( - fromSceneSize: IntSize, + fromContent: ContentKey, + toContent: ContentKey, orientation: Orientation, ): Float { // Foo is going to have a vertical offset of 50dp. Let's make the swipe distance diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index d31711496ff013d702940d352acb70a8d2d32ea6..70f2ff80f9d7ff14d93bd8daf45265a88c2f3aa3 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -22,17 +22,23 @@ import androidx.compose.animation.core.TweenSpec import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.unit.IntSize import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transformation.CustomPropertyTransformation import com.android.compose.animation.scene.transformation.OverscrollTranslate +import com.android.compose.animation.scene.transformation.PropertyTransformation +import com.android.compose.animation.scene.transformation.PropertyTransformationScope +import com.android.compose.animation.scene.transformation.TransformationMatcher import com.android.compose.animation.scene.transformation.TransformationRange -import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.test.transition import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test @@ -98,7 +104,7 @@ class TransitionDslTest { val transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } } val transformations = - transitions.transitionSpecs.single().transformationSpec(aToB()).transformations + transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers assertThat(transformations.size).isEqualTo(1) assertThat(transformations.single().range).isEqualTo(null) } @@ -121,7 +127,7 @@ class TransitionDslTest { } val transformations = - transitions.transitionSpecs.single().transformationSpec(aToB()).transformations + transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( @@ -152,7 +158,7 @@ class TransitionDslTest { } val transformations = - transitions.transitionSpecs.single().transformationSpec(aToB()).transformations + transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( @@ -180,7 +186,7 @@ class TransitionDslTest { } val transformations = - transitions.transitionSpecs.single().transformationSpec(aToB()).transformations + transitions.transitionSpecs.single().transformationSpec(aToB()).transformationMatchers assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) .containsExactly( @@ -210,7 +216,7 @@ class TransitionDslTest { // to B we defined. val transitionSpec = transitions.transitionSpec(from = SceneB, to = SceneA, key = null) - val transformations = transitionSpec.transformationSpec(aToB()).transformations + val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) @@ -220,7 +226,7 @@ class TransitionDslTest { ) val previewTransformations = - transitionSpec.previewTransformationSpec(aToB())?.transformations + transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers assertThat(previewTransformations) .comparingElementsUsing(TRANSFORMATION_RANGE) @@ -250,7 +256,7 @@ class TransitionDslTest { key = TransitionKey.PredictiveBack, ) - val transformations = transitionSpec.transformationSpec(aToB()).transformations + val transformations = transitionSpec.transformationSpec(aToB()).transformationMatchers assertThat(transformations) .comparingElementsUsing(TRANSFORMATION_RANGE) @@ -260,7 +266,7 @@ class TransitionDslTest { ) val previewTransformations = - transitionSpec.previewTransformationSpec(aToB())?.transformations + transitionSpec.previewTransformationSpec(aToB())?.transformationMatchers assertThat(previewTransformations) .comparingElementsUsing(TRANSFORMATION_RANGE) @@ -311,7 +317,7 @@ class TransitionDslTest { val overscrollSpec = transitions.overscrollSpecs.single() val transformation = - overscrollSpec.transformationSpec.transformations.single().transformation + overscrollSpec.transformationSpec.transformationMatchers.single().factory.create() assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java) } @@ -319,7 +325,7 @@ class TransitionDslTest { fun overscrollSpec_for_overscrollDisabled() { val transitions = transitions { overscrollDisabled(SceneA, Orientation.Vertical) } val overscrollSpec = transitions.overscrollSpecs.single() - assertThat(overscrollSpec.transformationSpec.transformations).isEmpty() + assertThat(overscrollSpec.transformationSpec.transformationMatchers).isEmpty() } @Test @@ -343,9 +349,36 @@ class TransitionDslTest { assertThat(transitionPassedToBuilder).isSameInstanceAs(transition) } + @Test + fun customTransitionsAreNotSupportedInRanges() = runTest { + val transitions = transitions { + from(SceneA, to = SceneB) { + fractionRange { + transformation(TestElements.Foo) { + object : CustomPropertyTransformation { + override val property = PropertyTransformation.Property.Size + + override fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): IntSize = IntSize.Zero + } + } + } + } + } + + val state = MutableSceneTransitionLayoutState(SceneA, transitions) + assertThrows(IllegalStateException::class.java) { + runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) } + } + } + companion object { private val TRANSFORMATION_RANGE = - Correspondence.transforming, TransformationRange?>( + Correspondence.transforming( { it?.range }, "has range equal to", ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt index 313379f4c74b65235252feaad3bed0b4a8182ec9..0adb4809dd2d720f46d4596fc78d20fa83dcec21 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -157,7 +157,7 @@ abstract class BaseTransitionSubject( check("isUserInputOngoing").that(actual.isUserInputOngoing).isEqualTo(isUserInputOngoing) } - fun hasOverscrollSpec(): OverscrollSpec { + internal fun hasOverscrollSpec(): OverscrollSpec { check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNotNull() return actual.currentOverscrollSpec!! } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..444ec4ead5619b1f3b6f2c056ae00266563b64bd --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.compose.animation.scene.transformation + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.test.assertPositionInRootIsEqualTo +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.testTransition +import com.android.compose.test.assertSizeIsEqualTo +import kotlinx.coroutines.CoroutineScope +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CustomTransformationTest { + @get:Rule val rule = createComposeRule() + + @Test + fun customSize() { + /** A size transformation that adds [add] to the size of the transformed element(s). */ + class AddSizeTransformation(private val add: Dp) : CustomPropertyTransformation { + override val property = PropertyTransformation.Property.Size + + override fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): IntSize { + val idleSize = checkNotNull(element.targetSize(content)) + val progress = 1f - transition.progressTo(content) + val addPx = (add * progress).roundToPx() + return IntSize(width = idleSize.width + addPx, height = idleSize.height + addPx) + } + } + + rule.testTransition( + fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(40.dp, 20.dp)) }, + toSceneContent = {}, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + + // Add 80dp to the width and height of Foo. + transformation(TestElements.Foo) { AddSizeTransformation(80.dp) } + }, + ) { + before { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) } + at(0) { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) } + at(16) { onElement(TestElements.Foo).assertSizeIsEqualTo(60.dp, 40.dp) } + at(32) { onElement(TestElements.Foo).assertSizeIsEqualTo(80.dp, 60.dp) } + at(48) { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 80.dp) } + after { onElement(TestElements.Foo).assertDoesNotExist() } + } + } + + @Test + fun customOffset() { + /** An offset transformation that adds [add] to the offset of the transformed element(s). */ + class AddOffsetTransformation(private val add: Dp) : CustomPropertyTransformation { + override val property = PropertyTransformation.Property.Offset + + override fun PropertyTransformationScope.transform( + content: ContentKey, + element: ElementKey, + transition: TransitionState.Transition, + transitionScope: CoroutineScope, + ): Offset { + val idleOffset = checkNotNull(element.targetOffset(content)) + val progress = 1f - transition.progressTo(content) + val addPx = (add * progress).toPx() + return Offset(x = idleOffset.x + addPx, y = idleOffset.y + addPx) + } + } + + rule.testTransition( + fromSceneContent = { Box(Modifier.element(TestElements.Foo)) }, + toSceneContent = {}, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + + // Add 80dp to the offset of Foo. + transformation(TestElements.Foo) { AddOffsetTransformation(80.dp) } + }, + ) { + before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) } + at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) } + at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 20.dp) } + at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(40.dp, 40.dp) } + at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(60.dp, 60.dp) } + after { onElement(TestElements.Foo).assertDoesNotExist() } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt index 91079b89a56c88a709909fc41f86446db620eac9..28ea2d239b5404f20a38e49b7b553d42591f1216 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt @@ -53,7 +53,8 @@ class PriorityNestedScrollConnectionTest { object : FlingBehavior { override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { scrollBy(initialVelocity) - return initialVelocity / 2f + // returns the remaining velocity: 1/3 remained + 2/3 consumed + return initialVelocity / 3f } } @@ -207,11 +208,13 @@ class PriorityNestedScrollConnectionTest { val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f)) - assertThat(lastStop).isEqualTo(2f) + val initialVelocity = 2f + assertThat(lastStop).isEqualTo(initialVelocity) // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity. assertThat(lastScroll).isEqualTo(2f) - // customFlingBehavior returns half of the vertical velocity. - assertThat(consumed).isEqualTo(Velocity(0f, 1f)) + val remainingVelocity = initialVelocity / 3f + // customFlingBehavior returns 2/3 of the vertical velocity. + assertThat(consumed).isEqualTo(Velocity(0f, initialVelocity - remainingVelocity)) } @Test diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml new file mode 100644 index 0000000000000000000000000000000000000000..18073adf9e7a15e688bf15bf1aa074dd25d6d359 --- /dev/null +++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml @@ -0,0 +1,20 @@ + + + + 0dp + 8dp + \ No newline at end of file diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml new file mode 100644 index 0000000000000000000000000000000000000000..37cd590b979e7aaf2bac31d9a8d03e900ada5de7 --- /dev/null +++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml @@ -0,0 +1,21 @@ + + + + + 0dp + 62dp + \ No newline at end of file diff --git a/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1cf42c4b0f098183437f04dc04a3792ff10e5bd --- /dev/null +++ b/packages/SystemUI/customization/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,19 @@ + + + + 24dp + \ No newline at end of file diff --git a/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml new file mode 100644 index 0000000000000000000000000000000000000000..54dbeaa1a3dded08e71f7ccac526050922f80696 --- /dev/null +++ b/packages/SystemUI/customization/res/values-sw720dp-port/dimens.xml @@ -0,0 +1,19 @@ + + + + 124dp + \ No newline at end of file diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml index c574d1fc674b19892aa0ebebae15da8e0b6bd9f6..041ae62670e5b5eba76d0998e05f9780b30ae406 100644 --- a/packages/SystemUI/customization/res/values/dimens.xml +++ b/packages/SystemUI/customization/res/values/dimens.xml @@ -33,4 +33,11 @@ 114dp 28dp 28dp + + + 12dp + + 24dp + 104dp + 0dp \ No newline at end of file diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt deleted file mode 100644 index 2a2d33308d12286bccfa86ae4525388a8470afbd..0000000000000000000000000000000000000000 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.clocks - -import android.content.Context -import android.content.res.Resources -import android.graphics.Typeface -import android.graphics.drawable.Drawable -import android.util.TypedValue -import com.android.internal.policy.SystemBarUtils -import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.monet.Style as MonetStyle -import java.io.IOException - -class AssetLoader -private constructor( - private val pluginCtx: Context, - private val sysuiCtx: Context, - private val baseDir: String, - var seedColor: Int?, - var overrideChroma: Float?, - val typefaceCache: TypefaceCache, - messageBuffer: MessageBuffer, -) { - val logger = Logger(messageBuffer, TAG) - private val resources = - listOf( - Pair(pluginCtx.resources, pluginCtx.packageName), - Pair(sysuiCtx.resources, sysuiCtx.packageName), - ) - - constructor( - pluginCtx: Context, - sysuiCtx: Context, - baseDir: String, - messageBuffer: MessageBuffer, - ) : this( - pluginCtx, - sysuiCtx, - baseDir, - seedColor = null, - overrideChroma = null, - typefaceCache = - TypefaceCache(messageBuffer) { - // TODO(b/364680873): Move constant to config_clockFontFamily when shipping - return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL) - }, - messageBuffer = messageBuffer, - ) - - fun listAssets(path: String): List { - return pluginCtx.resources.assets.list("$baseDir$path")?.toList() ?: emptyList() - } - - fun tryReadString(resStr: String): String? = tryRead(resStr, ::readString) - - fun readString(resStr: String): String { - val resPair = resolveResourceId(resStr) - if (resPair == null) { - throw IOException("Failed to parse string: $resStr") - } - - val (res, id) = resPair - return res.getString(id) - } - - fun readFontAsset(resStr: String): Typeface = typefaceCache.getTypeface(resStr) - - fun tryReadTextAsset(path: String?): String? = tryRead(path, ::readTextAsset) - - fun readTextAsset(path: String): String { - return pluginCtx.resources.assets.open("$baseDir$path").use { stream -> - val buffer = ByteArray(stream.available()) - stream.read(buffer) - String(buffer) - } - } - - fun tryReadDrawableAsset(path: String?): Drawable? = tryRead(path, ::readDrawableAsset) - - fun readDrawableAsset(path: String): Drawable { - var result: Drawable? - - if (path.startsWith("@")) { - val pair = resolveResourceId(path) - if (pair == null) { - throw IOException("Failed to parse $path to an id") - } - val (res, id) = pair - result = res.getDrawable(id) - } else if (path.endsWith("xml")) { - // TODO(b/248609434): Support xml files in assets - throw IOException("Cannot load xml files from assets") - } else { - // Attempt to load as if it's a bitmap and directly loadable - result = - pluginCtx.resources.assets.open("$baseDir$path").use { stream -> - Drawable.createFromResourceStream( - pluginCtx.resources, - TypedValue(), - stream, - null, - ) - } - } - - return result ?: throw IOException("Failed to load: $baseDir$path") - } - - fun parseResourceId(resStr: String): Triple { - if (!resStr.startsWith("@")) { - throw IOException("Invalid resource id: $resStr; Must start with '@'") - } - - // Parse out resource string - val parts = resStr.drop(1).split('/', ':') - return when (parts.size) { - 2 -> Triple(null, parts[0], parts[1]) - 3 -> Triple(parts[0], parts[1], parts[2]) - else -> throw IOException("Failed to parse resource string: $resStr") - } - } - - fun resolveResourceId(resStr: String): Pair? { - val (packageName, category, name) = parseResourceId(resStr) - return resolveResourceId(packageName, category, name) - } - - fun resolveResourceId( - packageName: String?, - category: String, - name: String, - ): Pair? { - for ((res, ctxPkgName) in resources) { - val result = res.getIdentifier(name, category, packageName ?: ctxPkgName) - if (result != 0) { - return Pair(res, result) - } - } - return null - } - - private fun tryRead(arg: TArg?, fn: (TArg) -> TRes): TRes? { - try { - if (arg == null) { - return null - } - return fn(arg) - } catch (ex: IOException) { - logger.w("Failed to read $arg", ex) - return null - } - } - - fun assetExists(path: String): Boolean { - try { - if (path.startsWith("@")) { - val pair = resolveResourceId(path) - return pair != null - } else { - val stream = pluginCtx.resources.assets.open("$baseDir$path") - if (stream == null) { - return false - } - - stream.close() - return true - } - } catch (ex: IOException) { - return false - } - } - - fun copy(messageBuffer: MessageBuffer? = null): AssetLoader = - AssetLoader( - pluginCtx, - sysuiCtx, - baseDir, - seedColor, - overrideChroma, - typefaceCache, - messageBuffer ?: logger.buffer, - ) - - fun setSeedColor(seedColor: Int?, style: MonetStyle?) { - this.seedColor = seedColor - } - - fun getClockPaddingStart(): Int { - val result = resolveResourceId(null, "dimen", "clock_padding_start") - if (result != null) { - val (res, id) = result - return res.getDimensionPixelSize(id) - } - return -1 - } - - fun getStatusBarHeight(): Int { - val display = pluginCtx.getDisplayNoVerify() - if (display != null) { - return SystemBarUtils.getStatusBarHeight(pluginCtx.resources, display.cutout) - } - - logger.w("No display available; falling back to android.R.dimen.status_bar_height") - val statusBarHeight = resolveResourceId("android", "dimen", "status_bar_height") - if (statusBarHeight != null) { - val (res, resId) = statusBarHeight - return res.getDimensionPixelSize(resId) - } - - throw Exception("Could not fetch StatusBarHeight") - } - - fun getResourcesId(name: String): Int = getResource("id", name) { _, id -> id } - - fun getDimen(name: String): Int = getResource("dimen", name, Resources::getDimensionPixelSize) - - fun getString(name: String): String = getResource("string", name, Resources::getString) - - private fun getResource( - category: String, - name: String, - getter: (res: Resources, id: Int) -> T, - ): T { - val result = resolveResourceId(null, category, name) - if (result != null) { - val (res, id) = result - if (id == -1) throw Exception("Cannot find id of $id from $TAG") - return getter(res, id) - } - throw Exception("Cannot find id of $name from $TAG") - } - - companion object { - private val TAG = AssetLoader::class.simpleName!! - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index 4ed8fd8f631d7d1a3ac9f5fe75526dbaa17fff0d..d0a32dcf98657ba7fa337e146090bfd696456aa2 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -16,12 +16,9 @@ package com.android.systemui.shared.clocks -import android.content.Context -import android.content.res.Resources import android.graphics.Rect import androidx.annotation.VisibleForTesting import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockEvents @@ -37,31 +34,22 @@ import java.util.Locale import java.util.TimeZone class ComposedDigitalLayerController( - private val ctx: Context, - private val resources: Resources, - private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + private val clockCtx: ClockContext, private val layer: ComposedDigitalHandLayer, - messageBuffer: MessageBuffer, ) : SimpleClockLayerController { - private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!) + private val logger = + Logger(clockCtx.messageBuffer, ComposedDigitalLayerController::class.simpleName!!) val layerControllers = mutableListOf() val dozeState = DefaultClockController.AnimationState(1F) - override val view = FlexClockView(ctx, assets, messageBuffer) + override val view = FlexClockView(clockCtx) init { layer.digitalLayers.forEach { - val childView = SimpleDigitalClockTextView(ctx, messageBuffer) + val childView = SimpleDigitalClockTextView(clockCtx) val controller = - SimpleDigitalHandLayerController( - ctx, - resources, - assets, - it as DigitalHandLayer, - childView, - messageBuffer, - ) + SimpleDigitalHandLayerController(clockCtx, it as DigitalHandLayer, childView) view.addView(childView) layerControllers.add(controller) @@ -156,8 +144,9 @@ class ComposedDigitalLayerController( val color = when { theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> resources.getColor(android.R.color.system_accent1_100) - else -> resources.getColor(android.R.color.system_accent2_600) + theme.isDarkTheme -> + clockCtx.resources.getColor(android.R.color.system_accent1_100) + else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) } view.updateColor(color) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index be4ebdfcb992137b3ac31ac08946a6333993ba5f..9bb92bc13416c25c11dbed24997b2c6a39838971 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -15,10 +15,10 @@ package com.android.systemui.shared.clocks import android.content.Context import android.content.res.Resources +import android.graphics.Typeface import android.view.LayoutInflater import com.android.systemui.customization.R -import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.core.LogcatOnlyMessageBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFontAxis import com.android.systemui.plugins.clocks.ClockFontAxisSetting @@ -33,6 +33,15 @@ import com.android.systemui.shared.clocks.view.VerticalAlignment private val TAG = DefaultClockProvider::class.simpleName const val DEFAULT_CLOCK_ID = "DEFAULT" +data class ClockContext( + val context: Context, + val resources: Resources, + val settings: ClockSettings, + val typefaceCache: TypefaceCache, + val messageBuffers: ClockMessageBuffers, + val messageBuffer: MessageBuffer, +) + /** Provides the default system clock */ class DefaultClockProvider( val ctx: Context, @@ -55,18 +64,20 @@ class DefaultClockProvider( } return if (isClockReactiveVariantsEnabled) { - val buffer = - messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO) - val assets = AssetLoader(ctx, ctx, "clocks/", buffer) - assets.setSeedColor(settings.seedColor, null) + val buffers = messageBuffers ?: ClockMessageBuffers(LogUtil.DEFAULT_MESSAGE_BUFFER) val fontAxes = ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes) + val clockSettings = settings.copy(axes = fontAxes.map { it.toSetting() }) + val typefaceCache = TypefaceCache(buffers.infraMessageBuffer) { FLEX_TYPEFACE } FlexClockController( - ctx, - resources, - settings.copy(axes = fontAxes.map { it.toSetting() }), - assets, + ClockContext( + ctx, + resources, + clockSettings, + typefaceCache, + buffers, + buffers.infraMessageBuffer, + ), FLEX_DESIGN, - messageBuffers, ) } else { DefaultClockController( @@ -116,6 +127,11 @@ class DefaultClockProvider( ClockFontAxisSetting("slnt", 0f), ) + val FLEX_TYPEFACE by lazy { + // TODO(b/364680873): Move constant to config_clockFontFamily when shipping + Typeface.create("google-sans-flex-clock", Typeface.NORMAL) + } + val FLEX_DESIGN = run { val largeLayer = listOf( @@ -139,7 +155,7 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER, + VerticalAlignment.BASELINE, ), dateTimeFormat = "hh", ), @@ -158,7 +174,7 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER, + VerticalAlignment.BASELINE, ), dateTimeFormat = "hh", ), @@ -177,7 +193,7 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER, + VerticalAlignment.BASELINE, ), dateTimeFormat = "mm", ), @@ -196,7 +212,7 @@ class DefaultClockProvider( alignment = DigitalAlignment( HorizontalAlignment.CENTER, - VerticalAlignment.CENTER, + VerticalAlignment.BASELINE, ), dateTimeFormat = "mm", ), diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index 6c627e27a19be6dd94cb6c23be7478d409722ed3..c7a3f63e92e7c3321a40f8a5cee4fb3ab945979d 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -16,8 +16,6 @@ package com.android.systemui.shared.clocks -import android.content.Context -import android.content.res.Resources import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.AxisType @@ -26,8 +24,6 @@ import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFontAxis import com.android.systemui.plugins.clocks.ClockFontAxisSetting -import com.android.systemui.plugins.clocks.ClockMessageBuffers -import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData @@ -38,42 +34,28 @@ import java.util.TimeZone /** Controller for the default flex clock */ class FlexClockController( - private val ctx: Context, - private val resources: Resources, - private val settings: ClockSettings, - private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + private val clockCtx: ClockContext, val design: ClockDesign, // TODO(b/364680879): Remove when done inlining - val messageBuffers: ClockMessageBuffers?, ) : ClockController { - override val smallClock = run { - val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER + override val smallClock = FlexClockFaceController( - ctx, - resources, - assets.copy(messageBuffer = buffer), + clockCtx.copy(messageBuffer = clockCtx.messageBuffers.smallClockMessageBuffer), design.small ?: design.large!!, - false, - buffer, + isLargeClock = false, ) - } - override val largeClock = run { - val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER + override val largeClock = FlexClockFaceController( - ctx, - resources, - assets.copy(messageBuffer = buffer), + clockCtx.copy(messageBuffer = clockCtx.messageBuffers.largeClockMessageBuffer), design.large ?: design.small!!, - true, - buffer, + isLargeClock = true, ) - } override val config: ClockConfig by lazy { ClockConfig( DEFAULT_CLOCK_ID, - resources.getString(R.string.clock_default_name), - resources.getString(R.string.clock_default_description), + clockCtx.resources.getString(R.string.clock_default_name), + clockCtx.resources.getString(R.string.clock_default_description), isReactiveToTone = true, ) } @@ -125,8 +107,8 @@ class FlexClockController( } override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) { - val theme = ThemeConfig(isDarkTheme, assets.seedColor) - events.onFontAxesChanged(settings.axes) + val theme = ThemeConfig(isDarkTheme, clockCtx.settings.seedColor) + events.onFontAxesChanged(clockCtx.settings.axes) smallClock.run { events.onThemeChanged(theme) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index a4782acaed9b135e4731433ad798e7adc95cd03e..a8890e6aa93414a370164ef0b5f194f296782163 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -16,15 +16,12 @@ package com.android.systemui.shared.clocks -import android.content.Context -import android.content.res.Resources import android.graphics.Rect import android.view.Gravity import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import com.android.systemui.customization.R -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockEvents @@ -45,25 +42,19 @@ import kotlin.math.max // TODO(b/364680879): Merge w/ ComposedDigitalLayerController class FlexClockFaceController( - ctx: Context, - private val resources: Resources, - val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + clockCtx: ClockContext, face: ClockFace, private val isLargeClock: Boolean, - messageBuffer: MessageBuffer, ) : ClockFaceController { override val view: View get() = layerController.view - override val config = - ClockFaceConfig( - hasCustomPositionUpdatedAnimation = false // TODO(b/364673982) - ) + override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true) - override var theme = ThemeConfig(true, assets.seedColor) + override var theme = ThemeConfig(true, clockCtx.settings.seedColor) private val keyguardLargeClockTopMargin = - resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin) + clockCtx.resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin) val layerController: SimpleClockLayerController val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm") @@ -75,27 +66,27 @@ class FlexClockFaceController( layerController = if (isLargeClock) { - ComposedDigitalLayerController( - ctx, - resources, - assets, - layer as ComposedDigitalHandLayer, - messageBuffer, - ) + ComposedDigitalLayerController(clockCtx, layer as ComposedDigitalHandLayer) } else { - val childView = SimpleDigitalClockTextView(ctx, messageBuffer) - SimpleDigitalHandLayerController( - ctx, - resources, - assets, - layer as DigitalHandLayer, - childView, - messageBuffer, - ) + val childView = SimpleDigitalClockTextView(clockCtx) + SimpleDigitalHandLayerController(clockCtx, layer as DigitalHandLayer, childView) } layerController.view.layoutParams = lp } + /** See documentation at [FlexClockView.offsetGlyphsForStepClockAnimation]. */ + private fun offsetGlyphsForStepClockAnimation( + clockStartLeft: Int, + direction: Int, + fraction: Float, + ) { + (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation( + clockStartLeft, + direction, + fraction, + ) + } + override val layout: ClockFaceLayout = DefaultClockFaceLayout(view).apply { views[0].id = @@ -248,10 +239,12 @@ class FlexClockFaceController( override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) { layerController.animations.onPositionUpdated(fromLeft, direction, fraction) + if (isLargeClock) offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction) } override fun onPositionUpdated(distance: Float, fraction: Float) { layerController.animations.onPositionUpdated(distance, fraction) + // TODO(b/378128811) port stepping animation } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index 143b28f1e82b380ffe93de3ea1be9bf55066a098..ebac4b24732bf592693838f628307f1eabb62849 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -16,8 +16,6 @@ package com.android.systemui.shared.clocks -import android.content.Context -import android.content.res.Resources import android.graphics.Rect import android.view.View import android.view.ViewGroup @@ -25,7 +23,6 @@ import android.widget.RelativeLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockEvents @@ -42,14 +39,11 @@ import java.util.TimeZone private val TAG = SimpleDigitalHandLayerController::class.simpleName!! open class SimpleDigitalHandLayerController( - private val ctx: Context, - private val resources: Resources, - private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources + private val clockCtx: ClockContext, private val layer: DigitalHandLayer, override val view: T, - messageBuffer: MessageBuffer, ) : SimpleClockLayerController where T : View, T : SimpleDigitalClockView { - private val logger = Logger(messageBuffer, TAG) + private val logger = Logger(clockCtx.messageBuffer, TAG) val timespec = DigitalTimespecHandler(layer.timespec, layer.dateTimeFormat) @VisibleForTesting @@ -75,12 +69,12 @@ open class SimpleDigitalHandLayerController( layer.alignment.verticalAlignment?.let { view.verticalAlignment = it } layer.alignment.horizontalAlignment?.let { view.horizontalAlignment = it } } - view.applyStyles(assets, layer.style, layer.aodStyle) + view.applyStyles(layer.style, layer.aodStyle) view.id = - ctx.resources.getIdentifier( + clockCtx.resources.getIdentifier( generateDigitalLayerIdString(layer), "id", - ctx.getPackageName(), + clockCtx.context.getPackageName(), ) } @@ -306,8 +300,9 @@ open class SimpleDigitalHandLayerController( val color = when { theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> resources.getColor(android.R.color.system_accent1_100) - else -> resources.getColor(android.R.color.system_accent2_600) + theme.isDarkTheme -> + clockCtx.resources.getColor(android.R.color.system_accent1_100) + else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) } view.updateColor(color) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt index b09332f34e99f861d5df5977eb1c8365883ebb63..d4eb7677c757886edcda77732fdcf6fcd20b1e76 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt @@ -16,24 +16,23 @@ package com.android.systemui.shared.clocks.view -import android.content.Context import android.graphics.Canvas import android.graphics.Point import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData +import com.android.systemui.shared.clocks.ClockContext import com.android.systemui.shared.clocks.LogUtil import java.util.Locale // TODO(b/364680879): Merge w/ only subclass FlexClockView -abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) { - protected val logger = Logger(messageBuffer, this::class.simpleName!!) +abstract class DigitalClockFaceView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) { + protected val logger = Logger(clockCtx.messageBuffer, this::class.simpleName!!) get() = field ?: LogUtil.FALLBACK_INIT_LOGGER abstract var digitalClockTextViewMap: MutableMap diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index d86c0d66459078170a97d6fa646262d501f15710..faef18c1e85af51e8288fddb69755afd680bdde6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -16,30 +16,34 @@ package com.android.systemui.shared.clocks.view -import android.content.Context import android.graphics.Canvas import android.graphics.Point +import android.icu.text.NumberFormat +import android.util.MathUtils.constrainedMap import android.view.View import android.view.ViewGroup import android.widget.RelativeLayout import com.android.app.animation.Interpolators import com.android.systemui.customization.R -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.shared.clocks.AssetLoader +import com.android.systemui.shared.clocks.ClockContext import com.android.systemui.shared.clocks.DigitTranslateAnimator +import java.util.Locale import kotlin.math.abs import kotlin.math.max import kotlin.math.min fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal) -class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: MessageBuffer) : - DigitalClockFaceView(context, messageBuffer) { +class FlexClockView(clockCtx: ClockContext) : DigitalClockFaceView(clockCtx) { override var digitalClockTextViewMap = mutableMapOf() - val digitLeftTopMap = mutableMapOf() - var maxSingleDigitSize = Point(-1, -1) - val lockscreenTranslate = Point(0, 0) - var aodTranslate = Point(0, 0) + private val digitLeftTopMap = mutableMapOf() + + private var maxSingleDigitSize = Point(-1, -1) + private val lockscreenTranslate = Point(0, 0) + private var aodTranslate = Point(0, 0) + + // Does the current language have mono vertical size when displaying numerals + private var isMonoVerticalNumericLineSpacing = true init { setWillNotDraw(false) @@ -48,8 +52,11 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ) + updateLocale(Locale.getDefault()) } + private val digitOffsets = mutableMapOf() + override fun addView(child: View?) { super.addView(child) (child as SimpleDigitalClockTextView).digitTranslateAnimator = @@ -58,12 +65,19 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point { maxSingleDigitSize = Point(-1, -1) + val bottomLocation: (textView: SimpleDigitalClockTextView) -> Int = { textView -> + if (isMonoVerticalNumericLineSpacing) { + maxSingleDigitSize.y + } else { + (textView.paint.fontMetrics.descent - textView.paint.fontMetrics.ascent).toInt() + } + } + digitalClockTextViewMap.forEach { (_, textView) -> textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth) - maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight) + maxSingleDigitSize.y = max(bottomLocation(textView), textView.measuredHeight) } - val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!! aodTranslate = Point(0, 0) return Point( ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2), @@ -76,7 +90,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0) digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y) digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize) - digitLeftTopMap.forEach { _, point -> + digitLeftTopMap.forEach { (_, point) -> point.x += abs(aodTranslate.x) point.y += abs(aodTranslate.y) } @@ -89,14 +103,25 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - digitalClockTextViewMap.forEach { (id, _) -> - val textView = digitalClockTextViewMap[id]!! - canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat()) + digitalClockTextViewMap.forEach { (id, textView) -> + // save canvas location in anticipation of restoration later + canvas.save() + val xTranslateAmount = + digitOffsets.getOrDefault(id, 0f) + digitLeftTopMap[id]!!.x.toFloat() + // move canvas to location that the textView would like + canvas.translate(xTranslateAmount, digitLeftTopMap[id]!!.y.toFloat()) + // draw the textView at the location of the canvas above textView.draw(canvas) - canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat()) + // reset the canvas location back to 0 without drawing + canvas.restore() } } + override fun onLocaleChanged(locale: Locale) { + updateLocale(locale) + requestLayout() + } + override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) { dozeControlState.animateDoze = { super.animateDoze(isDozing, isAnimated) @@ -157,10 +182,128 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me } } + private fun updateLocale(locale: Locale) { + isMonoVerticalNumericLineSpacing = + !NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES.any { + val newLocaleNumberFormat = + NumberFormat.getInstance(locale).format(FORMAT_NUMBER.toLong()) + val nonMonoVerticalNumericLineSpaceNumberFormat = + NumberFormat.getInstance(Locale.forLanguageTag(it)) + .format(FORMAT_NUMBER.toLong()) + newLocaleNumberFormat == nonMonoVerticalNumericLineSpaceNumberFormat + } + } + + /** + * Offsets the textViews of the clock for the step clock animation. + * + * The animation makes the textViews of the clock move at different speeds, when the clock is + * moving horizontally. + * + * @param clockStartLeft the [getLeft] position of the clock, before it started moving. + * @param clockMoveDirection the direction in which it is moving. A positive number means right, + * and negative means left. + * @param moveFraction fraction of the clock movement. 0 means it is at the beginning, and 1 + * means it finished moving. + */ + fun offsetGlyphsForStepClockAnimation( + clockStartLeft: Int, + clockMoveDirection: Int, + moveFraction: Float, + ) { + val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0 + // The sign of moveAmountDeltaForDigit is already set here + // we can interpret (left - clockStartLeft) as (destinationPosition - originPosition) + // so we no longer need to multiply direct sign to moveAmountDeltaForDigit + val currentMoveAmount = left - clockStartLeft + for (i in 0 until NUM_DIGITS) { + val mapIndexToId = + when (i) { + 0 -> R.id.HOUR_FIRST_DIGIT + 1 -> R.id.HOUR_SECOND_DIGIT + 2 -> R.id.MINUTE_FIRST_DIGIT + 3 -> R.id.MINUTE_SECOND_DIGIT + else -> -1 + } + val digitFraction = + getDigitFraction( + digit = i, + isMovingToCenter = isMovingToCenter, + fraction = moveFraction, + ) + // left here is the final left position after the animation is done + val moveAmountForDigit = currentMoveAmount * digitFraction + var moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount + if (isMovingToCenter && moveAmountForDigit < 0) moveAmountDeltaForDigit *= -1 + digitOffsets[mapIndexToId] = moveAmountDeltaForDigit + invalidate() + } + } + + private val moveToCenterDelays: List + get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS + + private val moveToSideDelays: List + get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS + + private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float { + // The delay for the digit, in terms of fraction. + // (i.e. the digit should not move during 0.0 - 0.1). + val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays + val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP + return MOVE_INTERPOLATOR.getInterpolation( + constrainedMap( + /* rangeMin= */ 0.0f, + /* rangeMax= */ 1.0f, + /* valueMin= */ digitInitialDelay, + /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME, + /* value= */ fraction, + ) + ) + } + companion object { val AOD_TRANSITION_DURATION = 750L val CHARGING_TRANSITION_DURATION = 300L + // Calculate the positions of all of the digits... + // Offset each digit by, say, 0.1 + // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should + // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3 + // from 0.3 - 1.0. + private const val NUM_DIGITS = 4 + + // Delays. Each digit's animation should have a slight delay, so we get a nice + // "stepping" effect. When moving right, the second digit of the hour should move first. + // When moving left, the first digit of the hour should move first. The lists encode + // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied + // by delayMultiplier. + private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3) + private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2) + + // How much delay to apply to each subsequent digit. This is measured in terms of "fraction" + // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc + // before moving). + // + // The current specs dictate that each digit should have a 33ms gap between them. The + // overall time is 1s right now. + private const val MOVE_DIGIT_STEP = 0.033f + + // Constants for the animation + private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED + + private const val FORMAT_NUMBER = 1234567890 + + // Total available transition time for each digit, taking into account the step. If step is + // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7. + private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1) + + // Add language tags below that do not have vertically mono spaced numerals + private val NON_MONO_VERTICAL_NUMERIC_LINE_SPACING_LANGUAGES = + setOf( + "my", // Burmese + ) + // Use the sign of targetTranslation to control the direction of digit translation fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point { val outPoint = Point(targetTranslation) @@ -169,17 +312,14 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me outPoint.x *= -1 outPoint.y *= -1 } - R.id.HOUR_SECOND_DIGIT -> { outPoint.x *= 1 outPoint.y *= -1 } - R.id.MINUTE_FIRST_DIGIT -> { outPoint.x *= -1 outPoint.y *= 1 } - R.id.MINUTE_SECOND_DIGIT -> { outPoint.x *= 1 outPoint.y *= 1 diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 5c84f2d04ccccca1a1399b36b0253720cb78ffc7..48761c081b9e4ecedf3f15ab097550bd2c0e481a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -17,7 +17,6 @@ package com.android.systemui.shared.clocks.view import android.annotation.SuppressLint -import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint @@ -37,13 +36,11 @@ import android.view.animation.Interpolator import android.widget.TextView import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.TextAnimator -import com.android.systemui.animation.TypefaceVariantCache import com.android.systemui.customization.R import com.android.systemui.log.core.Logger -import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.ClockFontAxisSetting -import com.android.systemui.shared.clocks.AssetLoader import com.android.systemui.shared.clocks.ClockAnimation +import com.android.systemui.shared.clocks.ClockContext import com.android.systemui.shared.clocks.DigitTranslateAnimator import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FontTextStyle @@ -57,18 +54,15 @@ import kotlin.math.min private val TAG = SimpleDigitalClockTextView::class.simpleName!! @SuppressLint("AppCompatCustomView") -open class SimpleDigitalClockTextView( - ctx: Context, - messageBuffer: MessageBuffer, - attrs: AttributeSet? = null, -) : TextView(ctx, attrs), SimpleDigitalClockView { +open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSet? = null) : + TextView(clockCtx.context, attrs), SimpleDigitalClockView { val lockScreenPaint = TextPaint() override lateinit var textStyle: FontTextStyle lateinit var aodStyle: FontTextStyle private var lsFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_LS_VARIATION) private var aodFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_AOD_VARIATION) - private val parser = DimensionParser(ctx) + private val parser = DimensionParser(clockCtx.context) var maxSingleDigitHeight = -1 var maxSingleDigitWidth = -1 var digitTranslateAnimator: DigitTranslateAnimator? = null @@ -91,33 +85,23 @@ open class SimpleDigitalClockTextView( private val prevTextBounds = Rect() // targetTextBounds holds the state we are interpolating to private val targetTextBounds = Rect() - protected val logger = Logger(messageBuffer, this::class.simpleName!!) + protected val logger = Logger(clockCtx.messageBuffer, this::class.simpleName!!) get() = field ?: LogUtil.FALLBACK_INIT_LOGGER private var aodDozingInterpolator: Interpolator? = null @VisibleForTesting lateinit var textAnimator: TextAnimator - lateinit var typefaceCache: TypefaceVariantCache - private set - - private fun setTypefaceCache(value: TypefaceVariantCache) { - typefaceCache = value - if (this::textAnimator.isInitialized) { - textAnimator.typefaceCache = value - } - } + private val typefaceCache = clockCtx.typefaceCache.getVariantCache("") @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> TextAnimator(layout, ClockAnimation.NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb).also { - if (this::typefaceCache.isInitialized) { - it.typefaceCache = typefaceCache - } + it.typefaceCache = typefaceCache } } - override var verticalAlignment: VerticalAlignment = VerticalAlignment.CENTER + override var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE override var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT override var isAnimationEnabled = true override var dozeFraction: Float = 0F @@ -258,7 +242,7 @@ open class SimpleDigitalClockTextView( -translation.y.toFloat(), (-translation.x + measuredWidth).toFloat(), (-translation.y + measuredHeight).toFloat(), - Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) }, + PORTER_DUFF_XFER_MODE_PAINT, ) canvas.restore() canvas.restore() @@ -403,7 +387,7 @@ open class SimpleDigitalClockTextView( // translation of reference point of text // used for translation when calling textInterpolator - fun getLocalTranslation(): Point { + private fun getLocalTranslation(): Point { val viewHeight = if (isVertical) measuredWidth else measuredHeight val interpolatedTextBounds = updateInterpolatedTextBounds() val localTranslation = Point(0, 0) @@ -429,17 +413,18 @@ open class SimpleDigitalClockTextView( correctedBaseline } VerticalAlignment.BASELINE -> { - localTranslation.y = -lockScreenPaint.strokeWidth.toInt() + // account for max bottom distance of font, so clock doesn't collide with elements + localTranslation.y = + -lockScreenPaint.strokeWidth.toInt() - paint.fontMetrics.descent.toInt() } } return updateXtranslation(localTranslation, interpolatedTextBounds) } - override fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?) { + override fun applyStyles(textStyle: TextStyle, aodStyle: TextStyle?) { this.textStyle = textStyle as FontTextStyle val typefaceName = "fonts/" + textStyle.fontFamily - setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName)) lockScreenPaint.strokeJoin = Paint.Join.ROUND lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) textStyle.fontFeatureSettings?.let { @@ -550,7 +535,9 @@ open class SimpleDigitalClockTextView( } companion object { - val AOD_STROKE_WIDTH = "2dp" + private val PORTER_DUFF_XFER_MODE_PAINT = + Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) } + val AOD_COLOR = Color.WHITE val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f) val DEFAULT_LS_VARIATION = diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt index 3d2ed4a1ef407b600fb24e4ec7b5235b4ea66c27..e8be28fb8df267f690f37da7e2af33534dd42bd9 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt @@ -18,7 +18,6 @@ package com.android.systemui.shared.clocks.view import androidx.annotation.VisibleForTesting import com.android.systemui.plugins.clocks.ClockFontAxisSetting -import com.android.systemui.shared.clocks.AssetLoader import com.android.systemui.shared.clocks.TextStyle interface SimpleDigitalClockView { @@ -29,7 +28,7 @@ interface SimpleDigitalClockView { val textStyle: TextStyle @VisibleForTesting var isAnimationEnabled: Boolean - fun applyStyles(assets: AssetLoader, textStyle: TextStyle, aodStyle: TextStyle?) + fun applyStyles(textStyle: TextStyle, aodStyle: TextStyle?) fun applyTextSize(targetFontSizePx: Float?, constrainedByHeight: Boolean = false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java deleted file mode 100644 index dd58ea7db2bce62546ed3716b96cac7d5e145dec..0000000000000000000000000000000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.keyguard; - -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.hardware.display.DisplayManagerGlobal; -import android.testing.TestableLooper; -import android.view.Display; -import android.view.DisplayInfo; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.navigationbar.NavigationBarController; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.concurrent.Executor; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class KeyguardDisplayManagerTest extends SysuiTestCase { - - @Mock - private NavigationBarController mNavigationBarController; - @Mock - private ConnectedDisplayKeyguardPresentation.Factory - mConnectedDisplayKeyguardPresentationFactory; - @Mock - private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation; - @Mock - private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper; - @Mock - private KeyguardStateController mKeyguardStateController; - - private Executor mMainExecutor = Runnable::run; - private Executor mBackgroundExecutor = Runnable::run; - private KeyguardDisplayManager mManager; - private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - // The default and secondary displays are both in the default group - private Display mDefaultDisplay; - private Display mSecondaryDisplay; - - // This display is in a different group from the default and secondary displays. - private Display mAlwaysUnlockedDisplay; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController, - mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper, - mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory)); - doReturn(mConnectedDisplayKeyguardPresentation).when( - mConnectedDisplayKeyguardPresentationFactory).create(any()); - doReturn(mConnectedDisplayKeyguardPresentation).when(mManager) - .createPresentation(any()); - mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, - new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY + 1, - new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS); - - DisplayInfo alwaysUnlockedDisplayInfo = new DisplayInfo(); - alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2; - alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED; - mAlwaysUnlockedDisplay = new Display(DisplayManagerGlobal.getInstance(), - Display.DEFAULT_DISPLAY, - alwaysUnlockedDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - } - - @Test - public void testShow_defaultDisplayOnly() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay}); - mManager.show(); - verify(mManager, never()).createPresentation(any()); - } - - @Test - public void testShow_includeSecondaryDisplay() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - mManager.show(); - verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_includeAlwaysUnlockedDisplay() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay}); - - mManager.show(); - verify(mManager, never()).createPresentation(any()); - } - - @Test - public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() { - mDisplayTracker.setAllDisplays( - new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay}); - - mManager.show(); - verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_concurrentDisplayActive_occluded() { - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - - when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true); - when(mKeyguardStateController.isOccluded()).thenReturn(true); - verify(mManager, never()).createPresentation(eq(mSecondaryDisplay)); - } - - @Test - public void testShow_presentationCreated() { - when(mManager.createPresentation(any())).thenCallRealMethod(); - mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay}); - - mManager.show(); - - verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay)); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..85bdf9264467cb5c4106a8155c5f1b93ce9eaa5c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyguard + +import android.hardware.display.DisplayManagerGlobal +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.DisplayAdjustments +import android.view.DisplayInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.navigationbar.NavigationBarController +import com.android.systemui.settings.FakeDisplayTracker +import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository +import com.android.systemui.statusbar.policy.KeyguardStateController +import java.util.concurrent.Executor +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.reset +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +class KeyguardDisplayManagerTest : SysuiTestCase() { + @Mock private val navigationBarController = mock(NavigationBarController::class.java) + @Mock + private val presentationFactory = mock(ConnectedDisplayKeyguardPresentation.Factory::class.java) + @Mock + private val connectedDisplayKeyguardPresentation = + mock(ConnectedDisplayKeyguardPresentation::class.java) + @Mock private val deviceStateHelper = mock(DeviceStateHelper::class.java) + @Mock private val keyguardStateController = mock(KeyguardStateController::class.java) + private val shadePositionRepository = FakeShadeDisplayRepository() + + private val mainExecutor = Executor { it.run() } + private val backgroundExecutor = Executor { it.run() } + private lateinit var manager: KeyguardDisplayManager + private val displayTracker = FakeDisplayTracker(mContext) + // The default and secondary displays are both in the default group + private lateinit var defaultDisplay: Display + private lateinit var secondaryDisplay: Display + + private val testScope = TestScope(UnconfinedTestDispatcher()) + + // This display is in a different group from the default and secondary displays. + private lateinit var alwaysUnlockedDisplay: Display + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + manager = + KeyguardDisplayManager( + mContext, + { navigationBarController }, + displayTracker, + mainExecutor, + backgroundExecutor, + deviceStateHelper, + keyguardStateController, + presentationFactory, + { shadePositionRepository }, + testScope.backgroundScope, + ) + whenever(presentationFactory.create(any())).doReturn(connectedDisplayKeyguardPresentation) + + defaultDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + secondaryDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY + 1, + DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + + val alwaysUnlockedDisplayInfo = DisplayInfo() + alwaysUnlockedDisplayInfo.displayId = Display.DEFAULT_DISPLAY + 2 + alwaysUnlockedDisplayInfo.flags = Display.FLAG_ALWAYS_UNLOCKED + alwaysUnlockedDisplay = + Display( + DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, + alwaysUnlockedDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, + ) + } + + @Test + fun testShow_defaultDisplayOnly() { + displayTracker.allDisplays = arrayOf(defaultDisplay) + manager.show() + verify(presentationFactory, never()).create(any()) + } + + @Test + fun testShow_includeSecondaryDisplay() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + manager.show() + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_includeAlwaysUnlockedDisplay() { + displayTracker.allDisplays = arrayOf(defaultDisplay, alwaysUnlockedDisplay) + + manager.show() + verify(presentationFactory, never()).create(any()) + } + + @Test + fun testShow_includeSecondaryAndAlwaysUnlockedDisplays() { + displayTracker.allDisplays = + arrayOf(defaultDisplay, secondaryDisplay, alwaysUnlockedDisplay) + + manager.show() + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_concurrentDisplayActive_occluded() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + whenever(deviceStateHelper.isConcurrentDisplayActive(secondaryDisplay)).thenReturn(true) + whenever(keyguardStateController.isOccluded).thenReturn(true) + verify(presentationFactory, never()).create(eq(secondaryDisplay)) + } + + @Test + fun testShow_presentationCreated() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeMovesDisplay_newPresentationCreated() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + // Shade in the default display, we expect the presentation to be in the secondary only + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + verify(presentationFactory, never()).create(eq(defaultDisplay)) + reset(presentationFactory) + whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation) + + // Let's move it to the secondary display. We expect it will be added in the default + // one. + shadePositionRepository.setDisplayId(secondaryDisplay.displayId) + testScope.advanceUntilIdle() + + verify(presentationFactory).create(eq(defaultDisplay)) + reset(presentationFactory) + whenever(presentationFactory.create(any())).thenReturn(connectedDisplayKeyguardPresentation) + + // Let's move it back! it should be re-created (it means it was removed before) + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + testScope.advanceUntilIdle() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeInSecondaryDisplay_defaultOneHasPresentation() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + shadePositionRepository.setDisplayId(secondaryDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(defaultDisplay)) + } + + @Test + @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun show_shadeInDefaultDisplay_secondaryOneHasPresentation() { + displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay) + shadePositionRepository.setDisplayId(defaultDisplay.displayId) + + manager.show() + + verify(presentationFactory).create(eq(secondaryDisplay)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/BatteryMeterDrawableTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/BatteryMeterDrawableTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/BootCompleteCacheTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/BootCompleteCacheTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/CameraAvailabilityListenerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/CameraAvailabilityListenerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/FakeCameraProtectionLoader.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/FakeCameraProtectionLoader.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index fcb433b5db4e2f40e543f856c9191e193654677f..9d471f45a2933bbb2cf99bff8ce5ee5afc8b7cc7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -45,9 +45,9 @@ import android.os.Handler; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.Space; import android.widget.Spinner; @@ -166,6 +166,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true); when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true); when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true); + when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, "")); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); mContext.setMockPackageManager(mPackageManager); @@ -215,6 +216,18 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { TEST_LAUNCH_SOURCE_ID); } + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_noLiveCaption_noRelatedToolsInConfig_relatedToolLayoutGone() { + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{}); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + assertToolsUi(0); + } + @Test @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() { @@ -227,8 +240,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { setUpPairNewDeviceDialog(); mDialog.show(); - LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); - assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1); + assertToolsUi(1); } @Test @@ -251,8 +263,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { setUpPairNewDeviceDialog(); mDialog.show(); - LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); - assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2); + assertToolsUi(2); } @Test @@ -263,8 +274,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { setUpDeviceListDialog(); mDialog.show(); - Spinner spinner = (Spinner) getPresetSpinner(mDialog); - assertThat(spinner.getVisibility()).isEqualTo(View.GONE); + ViewGroup presetLayout = getPresetLayout(mDialog); + assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -276,8 +287,9 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { setUpDeviceListDialog(); mDialog.show(); - Spinner spinner = (Spinner) getPresetSpinner(mDialog); - assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE); + ViewGroup presetLayout = getPresetLayout(mDialog); + assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE); + Spinner spinner = getPresetSpinner(mDialog); assertThat(spinner.getSelectedItemPosition()).isEqualTo(0); } @@ -292,8 +304,9 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO); mTestableLooper.processAllMessages(); - Spinner spinner = (Spinner) getPresetSpinner(mDialog); - assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE); + ViewGroup presetLayout = getPresetLayout(mDialog); + assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE); + Spinner spinner = getPresetSpinner(mDialog); assertThat(spinner.getSelectedItemPosition()).isEqualTo(0); } @@ -359,14 +372,23 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { return dialog.requireViewById(R.id.pair_new_device_button); } - private View getRelatedToolsView(SystemUIDialog dialog) { - return dialog.requireViewById(R.id.related_tools_container); + private ViewGroup getToolsContainer(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.tools_container); + } + + private ViewGroup getToolsLayout(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.tools_layout); } - private View getPresetSpinner(SystemUIDialog dialog) { + private Spinner getPresetSpinner(SystemUIDialog dialog) { return dialog.requireViewById(R.id.preset_spinner); } + private ViewGroup getPresetLayout(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.preset_layout); + } + + private int countChildWithoutSpace(ViewGroup viewGroup) { int spaceCount = 0; for (int i = 0; i < viewGroup.getChildCount(); i++) { @@ -377,6 +399,15 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { return viewGroup.getChildCount() - spaceCount; } + private void assertToolsUi(int childCount) { + ViewGroup toolsContainer = getToolsContainer(mDialog); + assertThat(countChildWithoutSpace(toolsContainer)).isEqualTo(childCount); + + int targetVisibility = childCount == 0 ? View.GONE : View.VISIBLE; + ViewGroup toolsLayout = getToolsLayout(mDialog); + assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility); + } + @After public void reset() { if (mDialogDelegate != null) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 72163e4d771008f032ffd5ca6457b6c9dcb96d6f..f82c8b0e56e04ac467219301e58711f48b6e6421 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -31,7 +31,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever @@ -62,7 +63,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { private val testScope = kosmos.testScope private val clock = FakeSystemClock() private val userRepository = FakeUserRepository() - private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository + private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository private lateinit var underTest: AuthenticationRepository @@ -110,7 +111,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { .isEqualTo(AuthenticationMethodModel.None) currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin - mobileConnectionsRepository.isAnySimSecure.value = true + mobileConnectionsRepository.fake.isAnySimSecure.value = true assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim) assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim) } @@ -199,7 +200,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { } private fun setSecurityModeAndDispatchBroadcast( - securityMode: KeyguardSecurityModel.SecurityMode, + securityMode: KeyguardSecurityModel.SecurityMode ) { currentSecurityMode = securityMode dispatchBroadcast() @@ -208,23 +209,15 @@ class AuthenticationRepositoryTest : SysuiTestCase() { private fun dispatchBroadcast() { fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, - Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), ) } companion object { private val USER_INFOS = listOf( - UserInfo( - /* id= */ 100, - /* name= */ "First user", - /* flags= */ 0, - ), - UserInfo( - /* id= */ 101, - /* name= */ "Second user", - /* flags= */ 0, - ), + UserInfo(/* id= */ 100, /* name= */ "First user", /* flags= */ 0), + UserInfo(/* id= */ 101, /* name= */ "Second user", /* flags= */ 0), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt index e1421691a92dd22cb8eed93916f5aa8f4d899056..58fe2c9cbe57157f36ded4f0b079d75b27b963e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt @@ -36,6 +36,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 22 private const val OWNER_ID = 10 +private const val PASSWORD_ID = 30 private const val OPERATION_ID = 100L private const val MAX_ATTEMPTS = 5 @@ -247,7 +248,11 @@ class CredentialInteractorImplTest : SysuiTestCase() { private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin = BiometricPromptRequest.Credential.Pin( promptInfo(), - BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner), + BiometricUserInfo( + userId = USER_ID, + deviceCredentialOwnerId = credentialOwner, + userIdForPasswordEntry = PASSWORD_ID, + ), BiometricOperationInfo(OPERATION_ID), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index c803097134ded85adaa45c6c3f35263bc83f9782..d75c0138bcbf8d16c6d58533601ce8d05d89f274 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -36,6 +36,7 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.HapticFeedbackConstants @@ -97,13 +98,14 @@ import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters private const val USER_ID = 4 +private const val WORK_USER_ID = 100 private const val REQUEST_ID = 4L private const val CHALLENGE = 2L private const val DELAY = 1000L +private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo" private const val OP_PACKAGE_NAME_WITH_APP_LOGO = "biometric.testapp" -private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon" +private const val OP_PACKAGE_NAME_NO_LOGO_INFO = "biometric.testapp.nologoinfo" private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found" -private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -120,13 +122,15 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private val defaultLogoIconFromAppInfo = context.getDrawable(R.drawable.ic_android) private val defaultLogoIconFromActivityInfo = context.getDrawable(R.drawable.ic_add) + private val defaultLogoIconWithBadge = context.getDrawable(R.drawable.ic_alarm) private val logoResFromApp = R.drawable.ic_cake private val logoDrawableFromAppRes = context.getDrawable(logoResFromApp) private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565) private val defaultLogoDescriptionFromAppInfo = "Test Android App" private val defaultLogoDescriptionFromActivityInfo = "Test Coke App" + private val defaultLogoDescriptionWithBadge = "Work app" private val logoDescriptionFromApp = "Test Cake App" - private val packageNameForLogoWithOverrides = "should.use.overridden.logo" + private val authInteractionProperties = AuthInteractionProperties() /** Prompt panel size padding */ @@ -173,55 +177,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Before fun setup() { - // Set up default logo info and app customized info - whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt())) - .thenReturn(applicationInfoNoIconOrDescription) - whenever( - kosmos.packageManager.getApplicationInfo( - eq(OP_PACKAGE_NAME_WITH_APP_LOGO), - anyInt(), - ) - ) - .thenReturn(applicationInfoWithIconAndDescription) - whenever( - kosmos.packageManager.getApplicationInfo( - eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO), - anyInt(), - ) - ) - .thenReturn(applicationInfoWithIconAndDescription) - whenever( - kosmos.packageManager.getApplicationInfo( - eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), - anyInt(), - ) - ) - .thenThrow(NameNotFoundException()) - - whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo) - whenever(kosmos.iconProvider.getIcon(activityInfo)) - .thenReturn(defaultLogoIconFromActivityInfo) - whenever(activityInfo.loadLabel(kosmos.packageManager)) - .thenReturn(defaultLogoDescriptionFromActivityInfo) - - whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription)) - .thenReturn(defaultLogoIconFromAppInfo) - whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription)) - .thenReturn(defaultLogoDescriptionFromAppInfo) - whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription)) - .thenReturn(null) - whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription)) - .thenReturn("") - whenever(kosmos.packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) } - whenever(kosmos.packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) } - - context.setMockPackageManager(kosmos.packageManager) - overrideResource(logoResFromApp, logoDrawableFromAppRes) - overrideResource( - R.array.config_useActivityLogoForBiometricPrompt, - arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO), - ) - + setupLogo() overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth) overrideResource( R.dimen.biometric_dialog_fingerprint_icon_height, @@ -264,6 +220,74 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa .build() } + private fun setupLogo() { + // Set up app customized logo + overrideResource(logoResFromApp, logoDrawableFromAppRes) + + // Set up when activity info should be used + overrideResource( + R.array.config_useActivityLogoForBiometricPrompt, + arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO), + ) + whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo) + whenever(kosmos.iconProvider.getIcon(activityInfo)) + .thenReturn(defaultLogoIconFromActivityInfo) + whenever(activityInfo.loadLabel(kosmos.packageManager)) + .thenReturn(defaultLogoDescriptionFromActivityInfo) + + // Set up when application info should be used for default logo + whenever( + kosmos.packageManager.getApplicationInfo( + eq(OP_PACKAGE_NAME_WITH_APP_LOGO), + anyInt(), + ) + ) + .thenReturn(applicationInfoWithIconAndDescription) + whenever( + kosmos.packageManager.getApplicationInfo( + eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO), + anyInt(), + ) + ) + .thenReturn(applicationInfoWithIconAndDescription) + whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription)) + .thenReturn(defaultLogoIconFromAppInfo) + whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription)) + .thenReturn(defaultLogoDescriptionFromAppInfo) + + // Set up when package name cannot but found + whenever( + kosmos.packageManager.getApplicationInfo( + eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), + anyInt(), + ) + ) + .thenThrow(NameNotFoundException()) + + // Set up when no default logo from application info + whenever( + kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_LOGO_INFO), anyInt()) + ) + .thenReturn(applicationInfoNoIconOrDescription) + whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription)) + .thenReturn(null) + whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription)) + .thenReturn("") + + // Set up work badge + whenever(kosmos.packageManager.getUserBadgedIcon(any(), eq(UserHandle.of(USER_ID)))).then { + it.getArgument(0) + } + whenever(kosmos.packageManager.getUserBadgedLabel(any(), eq(UserHandle.of(USER_ID)))).then { + it.getArgument(0) + } + whenever(kosmos.packageManager.getUserBadgedIcon(any(), eq(UserHandle.of(WORK_USER_ID)))) + .then { defaultLogoIconWithBadge } + whenever(kosmos.packageManager.getUserBadgedLabel(any(), eq(UserHandle.of(WORK_USER_ID)))) + .then { defaultLogoDescriptionWithBadge } + context.setMockPackageManager(kosmos.packageManager) + } + @Test fun start_idle_and_show_authenticating() = runGenericTest(doNotStart = true) { @@ -1520,6 +1544,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat(logoInfo).isNotNull() assertThat(logoInfo!!.first).isNull() + assertThat(logoInfo!!.second).isEqualTo("") + } + + @Test + fun logo_defaultIsNull() = + runGenericTest(packageName = OP_PACKAGE_NAME_NO_LOGO_INFO) { + val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) + assertThat(logoInfo).isNotNull() + assertThat(logoInfo!!.first).isNull() + assertThat(logoInfo!!.second).isEqualTo("") } @Test @@ -1527,32 +1561,39 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) + assertThat(logoInfo).isNotNull() // 1. PM.getApplicationInfo(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) is set to return // applicationInfoWithIconAndDescription with "defaultLogoIconFromAppInfo", // 2. iconProvider.getIcon(activityInfo) is set to return // "defaultLogoIconFromActivityInfo" // For the apps with OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO, 2 should be called instead of 1 - assertThat(logoInfo).isNotNull() assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromActivityInfo) + // 1. PM.getApplicationInfo(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) is set to return + // applicationInfoWithIconAndDescription with "defaultLogoDescriptionFromAppInfo", + // 2. activityInfo.loadLabel() is set to return defaultLogoDescriptionFromActivityInfo + // For the apps with OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO, 2 should be called instead of 1 + assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromActivityInfo) } @Test - fun logo_defaultIsNull() = - runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) - assertThat(logoInfo).isNotNull() - assertThat(logoInfo!!.first).isNull() - } - - @Test - fun logo_default() = runGenericTest { + fun logo_defaultFromApplicationInfo() = runGenericTest { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat(logoInfo).isNotNull() assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo) + assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo) } @Test - fun logo_resSetByApp() = + fun logo_defaultWithWorkBadge() = + runGenericTest(userId = WORK_USER_ID) { + val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) + assertThat(logoInfo).isNotNull() + assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconWithBadge) + assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionWithBadge) + } + + @Test + fun logoRes_setByApp() = runGenericTest(logoRes = logoResFromApp) { val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap() val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) @@ -1561,43 +1602,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logo_bitmapSetByApp() = + fun logoBitmap_setByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) assertThat((logoInfo!!.first as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) } - @Test - fun logoDescription_emptyIfPkgNameNotFound() = - runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { - val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) - assertThat(logoInfo!!.second).isEqualTo("") - } - - @Test - fun logoDescription_defaultFromActivityInfo() = - runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) { - val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) - // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return - // applicationInfoWithIconAndDescription with defaultLogoDescription, - // 2. activityInfo.loadLabel() is set to return defaultLogoDescriptionWithOverrides - // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1 - assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromActivityInfo) - } - - @Test - fun logoDescription_defaultIsEmpty() = - runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) - assertThat(logoInfo!!.second).isEqualTo("") - } - - @Test - fun logoDescription_default() = runGenericTest { - val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo) - assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo) - } - @Test fun logoDescription_setByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { @@ -1766,6 +1776,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa logoBitmap: Bitmap? = null, logoDescription: String? = null, packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO, + userId: Int = USER_ID, block: suspend TestScope.() -> Unit, ) { val topActivity = ComponentName(packageName, "test app") @@ -1785,6 +1796,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa logoBitmapFromApp = if (logoRes != 0) logoDrawableFromAppRes.toBitmap() else logoBitmap, logoDescriptionFromApp = logoDescription, packageName = packageName, + userId = userId, ) kosmos.biometricStatusRepository.setFingerprintAcquiredStatus( @@ -2010,6 +2022,7 @@ private fun PromptSelectorInteractor.initializePrompt( logoBitmapFromApp: Bitmap? = null, logoDescriptionFromApp: String? = null, packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO, + userId: Int = USER_ID, ) { val info = PromptInfo().apply { @@ -2028,7 +2041,7 @@ private fun PromptSelectorInteractor.initializePrompt( setPrompt( info, - USER_ID, + userId, REQUEST_ID, BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), CHALLENGE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt index beb816cae095db9c33dcc00838044c2e485f6b8b..32606e09a1acff9bec187a14380e02634c88148c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothDevice -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.LeAudioProfile import com.android.settingslib.bluetooth.LocalBluetoothProfileManager @@ -48,7 +48,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @OptIn(ExperimentalCoroutinesApi::class) class AudioSharingDialogViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt index c9e88136e4a0efffafc8f5521d8db333ad548b67..acfe9dd45f75601644b903b8e88180a9e8addaa5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothLeBroadcastMetadata -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant @@ -46,7 +46,7 @@ import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class AudioSharingRepositoryTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt index f06b105a9e264c5db297f04e17311df52997e2eb..cee17c32925e7831a9e64912bd22b7e417b81418 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt @@ -18,7 +18,9 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice +import android.graphics.drawable.Drawable import android.testing.TestableLooper +import android.util.Pair import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -64,6 +66,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() { @Mock private lateinit var bluetoothDevice1: BluetoothDevice @Mock private lateinit var cachedDevice2: CachedBluetoothDevice @Mock private lateinit var bluetoothDevice2: BluetoothDevice + @Mock private lateinit var drawable: Drawable @Captor private lateinit var argumentCaptor: ArgumentCaptor private lateinit var interactor: BluetoothDeviceMetadataInteractor @@ -77,12 +80,14 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() { whenever(cachedDevice1.name).thenReturn(DEVICE_NAME) whenever(cachedDevice1.address).thenReturn(DEVICE_ADDRESS) whenever(cachedDevice1.connectionSummary).thenReturn(CONNECTION_SUMMARY) + whenever(cachedDevice1.drawableWithDescription).thenReturn(Pair.create(drawable, "")) whenever(bluetoothDevice1.address).thenReturn(DEVICE_ADDRESS) whenever(cachedDevice2.device).thenReturn(bluetoothDevice2) whenever(cachedDevice2.name).thenReturn(DEVICE_NAME) whenever(cachedDevice2.address).thenReturn(DEVICE_ADDRESS) whenever(cachedDevice2.connectionSummary).thenReturn(CONNECTION_SUMMARY) + whenever(cachedDevice2.drawableWithDescription).thenReturn(Pair.create(drawable, "")) whenever(bluetoothDevice2.address).thenReturn(DEVICE_ADDRESS) interactor = bluetoothDeviceMetadataInteractor @@ -175,14 +180,14 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() { .addOnMetadataChangedListener( eq(bluetoothDevice1), any(), - argumentCaptor.capture() + argumentCaptor.capture(), ) val listener = argumentCaptor.value listener.onMetadataChanged( bluetoothDevice1, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY, - ByteArray(0) + ByteArray(0), ) assertThat(update).isEqualTo(Unit) } @@ -203,14 +208,14 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() { .addOnMetadataChangedListener( eq(bluetoothDevice1), any(), - argumentCaptor.capture() + argumentCaptor.capture(), ) val listener = argumentCaptor.value listener.onMetadataChanged( bluetoothDevice1, BluetoothDevice.METADATA_MODEL_NAME, - ByteArray(0) + ByteArray(0), ) assertThat(update).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 566cd70598de8d5b517a8d8ac35cb9b761989a1f..10bf5233e61ef1dceec31b88e55692e4bcabfe13 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -35,7 +35,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.telephony.data.repository.fakeTelephonyRepository import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -78,7 +79,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository + mobileConnectionsRepository = kosmos.mobileConnectionsRepository.fake overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL) overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt index 7cdfb0eb2451ae9e25f5dfb19d2edfc26977d81b..0f148f89e3c52eda8e05af945629d0914338208c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt @@ -30,11 +30,11 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory -import com.android.systemui.kosmos.brightnessWarningToast import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.settings.brightness.ui.brightnessWarningToast import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt index 82918a569990a796bf1667ba16229a105b109eb4..af2d7e45ee5f10a30a7053ed1750311281fcd5e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt @@ -47,20 +47,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logAddWidget_componentNotLoggable_doNotLog() { - underTest.logAddWidget( - componentName = "com.green.package/my_test_widget", - rank = 1, - ) + underTest.logAddWidget(componentName = "com.green.package/my_test_widget", rank = 1) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logAddWidget_componentLoggable_logAddEvent() { - underTest.logAddWidget( - componentName = "com.blue.package/my_test_widget", - rank = 1, - ) + underTest.logAddWidget(componentName = "com.blue.package/my_test_widget", rank = 1) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD, @@ -71,20 +65,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logRemoveWidget_componentNotLoggable_doNotLog() { - underTest.logRemoveWidget( - componentName = "com.yellow.package/my_test_widget", - rank = 2, - ) + underTest.logRemoveWidget(componentName = "com.yellow.package/my_test_widget", rank = 2) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logRemoveWidget_componentLoggable_logRemoveEvent() { - underTest.logRemoveWidget( - componentName = "com.red.package/my_test_widget", - rank = 2, - ) + underTest.logRemoveWidget(componentName = "com.red.package/my_test_widget", rank = 2) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE, @@ -95,20 +83,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logTapWidget_componentNotLoggable_doNotLog() { - underTest.logTapWidget( - componentName = "com.yellow.package/my_test_widget", - rank = 2, - ) + underTest.logTapWidget(componentName = "com.yellow.package/my_test_widget", rank = 2) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logTapWidget_componentLoggable_logRemoveEvent() { - underTest.logTapWidget( - componentName = "com.red.package/my_test_widget", - rank = 2, - ) + underTest.logTapWidget(componentName = "com.red.package/my_test_widget", rank = 2) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP, @@ -140,4 +122,43 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { ) assertThat(statsEvents).hasSize(1) } + + @Test + fun logResizeWidget_componentNotLoggable_doNotLog() { + underTest.logResizeWidget( + componentName = "com.green.package/my_test_widget", + rank = 1, + spanY = 2, + ) + verify(statsLogProxy, never()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) + } + + @Test + fun logResizeWidget_componentLoggable_logResizeEvent() { + underTest.logResizeWidget( + componentName = "com.blue.package/my_test_widget", + rank = 1, + spanY = 2, + ) + verify(statsLogProxy) + .writeCommunalHubWidgetEventReported( + SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE, + "com.blue.package/my_test_widget", + rank = 1, + spanY = 2, + ) + } + + @Test + fun logResizeWidget_defaultSpanY_usesDefaultValue() { + underTest.logResizeWidget(componentName = "com.blue.package/my_test_widget", rank = 1) + verify(statsLogProxy) + .writeCommunalHubWidgetEventReported( + SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE, + "com.blue.package/my_test_widget", + rank = 1, + spanY = 0, + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt index baeb2dd5aa9638c5f65a2ce3a3b28fa11570d810..0084e18f519f34c2e1f430c401f38a1b274676c5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt @@ -16,38 +16,64 @@ package com.android.systemui.communal.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val communalSceneRepository = kosmos.fakeCommunalSceneRepository + private val sceneInteractor = kosmos.sceneInteractor private val underTest: CommunalTransitionViewModel by lazy { kosmos.communalTransitionViewModel } + @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun testIsUmoOnCommunalDuringTransitionBetweenLockscreenAndGlanceableHub() = testScope.runTest { @@ -61,6 +87,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isFalse() } + @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun testIsUmoOnCommunalDuringTransitionBetweenDreamingAndGlanceableHub() = testScope.runTest { @@ -74,6 +101,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isFalse() } + @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() = testScope.runTest { @@ -87,6 +115,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isFalse() } + @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isUmoOnCommunal_noLongerVisible_returnsFalse() = testScope.runTest { @@ -103,6 +132,7 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isFalse() } + @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isUmoOnCommunal_idleOnCommunal_returnsTrue() = testScope.runTest { @@ -116,11 +146,25 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { assertThat(isUmoOnCommunal).isTrue() } + @EnableFlags(FLAG_SCENE_CONTAINER) + @Test + fun isUmoOnCommunal_sceneContainerEnabled_idleOnCommunal_returnsTrue() = + testScope.runTest { + val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal) + assertThat(isUmoOnCommunal).isFalse() + + // Change to communal scene. + setIdleScene(Scenes.Communal) + + // isUmoOnCommunal returns true, even without any keyguard transition. + assertThat(isUmoOnCommunal).isTrue() + } + private suspend fun TestScope.enterCommunal(from: KeyguardState) { keyguardTransitionRepository.sendTransitionSteps( from = from, to = KeyguardState.GLANCEABLE_HUB, - testScope + testScope, ) communalSceneRepository.changeScene(CommunalScenes.Communal) runCurrent() @@ -130,9 +174,17 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GLANCEABLE_HUB, to = to, - testScope + testScope, ) communalSceneRepository.changeScene(CommunalScenes.Blank) runCurrent() } + + private fun setIdleScene(scene: SceneKey) { + sceneInteractor.changeScene(scene, "test") + val transitionState = + MutableStateFlow(ObservableTransitionState.Idle(scene)) + sceneInteractor.setTransitionState(transitionState) + testScope.runCurrent() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index cecc11e5ffd45c2bf091c5ac704c391700839179..5bbd3ffc625a338a257325d08dae7ee28e961783 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.media.controls.ui.controller.mediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos @@ -133,6 +134,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { accessibilityManager, packageManager, WIDGET_PICKER_PACKAGE_NAME, + kosmos.mediaCarouselController, ) } @@ -353,6 +355,32 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen") } + @Test + fun onResizeWidget_logsMetrics() = + testScope.runTest { + val appWidgetId = 123 + val spanY = 2 + val widgetIdToRankMap = mapOf(appWidgetId to 1) + val componentName = ComponentName("test.package", "TestWidget") + val rank = 1 + + underTest.onResizeWidget( + appWidgetId = appWidgetId, + spanY = spanY, + widgetIdToRankMap = widgetIdToRankMap, + componentName = componentName, + rank = rank, + ) + + verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap) + verify(metricsLogger) + .logResizeWidget( + componentName = componentName.flattenToString(), + rank = rank, + spanY = spanY, + ) + } + private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 09daa51a3b37012f5de7aca4b48b5a2b6ac3caa6..3eba8ff4b198c48e4b4fad8342e63d781a983532 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -69,6 +69,7 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.mediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor @@ -178,6 +179,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { mediaHost, logcatLogBuffer("CommunalViewModelTest"), metricsLogger, + kosmos.mediaCarouselController, ) } @@ -627,10 +629,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.setTransition( sceneTransition = Idle(Scenes.Communal), stateTransition = - TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.GLANCEABLE_HUB - ), + TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB), ) // Then flow is not frozen @@ -647,10 +646,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.setTransition( sceneTransition = Idle(Scenes.Lockscreen), stateTransition = - TransitionStep( - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.OCCLUDED - ), + TransitionStep(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.OCCLUDED), ) // Then flow is not frozen @@ -735,10 +731,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.setTransition( sceneTransition = Idle(Scenes.Communal), stateTransition = - TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.GLANCEABLE_HUB - ), + TransitionStep(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB), ) // Widgets available diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index 38ed22a1c410aa931c7916ddd57f14305e8a1ad6..f0d79bb8365200e6626ac1e78485d9f04f4be8f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus @@ -40,6 +41,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.fakeUserRepository @@ -47,6 +50,7 @@ import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -230,11 +234,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { @Test fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() = testScope.runTest { - kosmos.fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - 0, - kosmos.selectedUserInteractor.getSelectedUserId(), - ) + setLockAfterScreenTimeout(0) kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) @@ -254,11 +254,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_afterDelay() = testScope.runTest { val delay = 5000 - kosmos.fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - delay, - kosmos.selectedUserInteractor.getSelectedUserId(), - ) + setLockAfterScreenTimeout(delay) kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) @@ -279,11 +275,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { @Test fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_powerButtonLocksInstantly() = testScope.runTest { - kosmos.fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - 5000, - kosmos.selectedUserInteractor.getSelectedUserId(), - ) + setLockAfterScreenTimeout(5000) kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = true val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) @@ -304,11 +296,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { @Test fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() = testScope.runTest { - kosmos.fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, - 0, - kosmos.selectedUserInteractor.getSelectedUserId(), - ) + setLockAfterScreenTimeout(0) val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) assertThat(deviceUnlockStatus?.isUnlocked).isFalse() @@ -517,6 +505,98 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate) } + @Test + fun deviceUnlockStatus_locksImmediately_whenDreamStarts_noTimeout() = + testScope.runTest { + setLockAfterScreenTimeout(0) + val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked }) + unlockDevice() + + startDreaming() + + assertThat(isUnlocked).isFalse() + } + + @Test + fun deviceUnlockStatus_locksWithDelay_afterDreamStarts_withTimeout() = + testScope.runTest { + val delay = 5000 + setLockAfterScreenTimeout(delay) + val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked }) + unlockDevice() + + startDreaming() + assertThat(isUnlocked).isTrue() + + advanceTimeBy(delay - 1L) + assertThat(isUnlocked).isTrue() + + advanceTimeBy(1L) + assertThat(isUnlocked).isFalse() + } + + @Test + fun deviceUnlockStatus_doesNotLockWithDelay_whenDreamStopsBeforeTimeout() = + testScope.runTest { + val delay = 5000 + setLockAfterScreenTimeout(delay) + val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked }) + unlockDevice() + + startDreaming() + assertThat(isUnlocked).isTrue() + + advanceTimeBy(delay - 1L) + assertThat(isUnlocked).isTrue() + + stopDreaming() + assertThat(isUnlocked).isTrue() + + advanceTimeBy(1L) + assertThat(isUnlocked).isTrue() + } + + @Test + fun deviceUnlockStatus_doesNotLock_whenDreamStarts_ifNotInteractive() = + testScope.runTest { + setLockAfterScreenTimeout(0) + val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked }) + unlockDevice() + + startDreaming() + + assertThat(isUnlocked).isFalse() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } + + private fun setLockAfterScreenTimeout(timeoutMs: Int) { + kosmos.fakeSettings.putIntForUser( + Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, + timeoutMs, + kosmos.selectedUserInteractor.getSelectedUserId(), + ) + } + + private fun TestScope.startDreaming() { + kosmos.fakeKeyguardRepository.setDreaming(true) + runCurrent() + } + + private fun TestScope.stopDreaming() { + kosmos.fakeKeyguardRepository.setDreaming(false) + runCurrent() + } + private fun TestScope.verifyRestrictionReasonsForAuthFlags( vararg authFlagToDeviceEntryRestriction: Pair ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..789178728f18b8d2465ef44ff9c90c95af9280ec --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/RearDisplayStateInteractorTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.display.domain.interactor + +import android.hardware.display.defaultDisplay +import android.hardware.display.rearDisplay +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.DeviceStateRepository +import com.android.systemui.display.data.repository.FakeDeviceStateRepository +import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.whenever + +/** atest RearDisplayStateInteractorTest */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class RearDisplayStateInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val fakeDisplayRepository = FakeDisplayRepository() + private val fakeDeviceStateRepository = FakeDeviceStateRepository() + private val rearDisplayStateInteractor = + RearDisplayStateInteractorImpl( + fakeDisplayRepository, + fakeDeviceStateRepository, + kosmos.testDispatcher, + ) + private val emissionTracker = EmissionTracker(rearDisplayStateInteractor, kosmos.testScope) + + @Before + fun setup() { + whenever(kosmos.rearDisplay.flags).thenReturn(Display.FLAG_REAR) + } + + @Test + fun enableRearDisplayWhenDisplayImmediatelyAvailable() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + assertThat(tracker.enabledCount).isEqualTo(1) + assertThat(tracker.lastDisplay).isEqualTo(kosmos.rearDisplay) + } + } + + @Test + fun enableAndDisableRearDisplay() = + kosmos.runTest { + emissionTracker.use { tracker -> + // The fake FakeDeviceStateRepository will always start with state UNKNOWN, thus + // triggering one initial emission + assertThat(tracker.disabledCount).isEqualTo(1) + + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + // Adding a non-rear display does not trigger an emission + fakeDisplayRepository.addDisplay(kosmos.defaultDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + + // Adding a rear display triggers the emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + assertThat(tracker.lastDisplay).isEqualTo(kosmos.rearDisplay) + + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + assertThat(tracker.disabledCount).isEqualTo(2) + } + } + + @Test + fun enableRearDisplayShouldOnlyReactToFirstRearDisplay() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + + // Adding a rear display triggers the emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + + // Adding additional rear displays does not trigger additional emissions + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(1) + } + } + + @Test + fun rearDisplayAddedWhenNoLongerInRdm() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit( + DeviceStateRepository.DeviceState.REAR_DISPLAY_OUTER_DEFAULT + ) + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + + // Adding a rear display when no longer in the correct device state does not trigger + // an emission + fakeDisplayRepository.addDisplay(kosmos.rearDisplay) + assertThat(tracker.enabledCount).isEqualTo(0) + } + } + + @Test + fun rearDisplayDisabledDoesNotSpam() = + kosmos.runTest { + emissionTracker.use { tracker -> + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNFOLDED) + assertThat(tracker.disabledCount).isEqualTo(1) + + // No additional emission + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.FOLDED) + assertThat(tracker.disabledCount).isEqualTo(1) + } + } + + class EmissionTracker(rearDisplayInteractor: RearDisplayStateInteractor, scope: TestScope) : + AutoCloseable { + var enabledCount = 0 + var disabledCount = 0 + var lastDisplay: Display? = null + + val job: Job + + init { + val channel = Channel(Channel.UNLIMITED) + job = + scope.launch { + rearDisplayInteractor.state.collect { + channel.send(it) + if (it is RearDisplayStateInteractor.State.Enabled) { + enabledCount++ + lastDisplay = it.innerDisplay + } + if (it is RearDisplayStateInteractor.State.Disabled) { + disabledCount++ + } + } + } + } + + override fun close() { + job.cancel() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java index eb72f29046aa9f158fe28d0cb564c9d22727b1a1..334718049830618c7bda24250d2eb0beb99b140b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java @@ -48,10 +48,10 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; -import androidx.test.annotation.UiThreadTest; import android.view.Display; import androidx.annotation.NonNull; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index 8062358f670c974734b853e9d5dbe4d9e16c9d82..a65e7ed487976271e9c7883b42e6efc682a1e45b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -19,7 +19,9 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.content.Intent import android.os.RemoteException +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.service.dreams.Flags import android.service.dreams.IDreamOverlay import android.service.dreams.IDreamOverlayCallback @@ -33,7 +35,6 @@ import android.view.WindowManagerImpl import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.app.viewcapture.ViewCapture import com.android.app.viewcapture.ViewCaptureAwareWindowManager @@ -43,6 +44,7 @@ import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent @@ -62,12 +64,16 @@ import com.android.systemui.complication.ComplicationLayoutEngine import com.android.systemui.complication.dagger.ComplicationComponent import com.android.systemui.dreams.complication.HideComplicationTouchHandler import com.android.systemui.dreams.dagger.DreamOverlayComponent +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.gesture.domain.gestureInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.navigationbar.gestural.domain.GestureInteractor import com.android.systemui.navigationbar.gestural.domain.TaskInfo import com.android.systemui.navigationbar.gestural.domain.TaskMatcher +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.touch.TouchInsetManager import com.android.systemui.util.concurrency.FakeExecutor @@ -98,12 +104,14 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidJUnit4::class) -class DreamOverlayServiceTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { private val mFakeSystemClock = FakeSystemClock() private val mMainExecutor = FakeExecutor(mFakeSystemClock) private val kosmos = testKosmos() @@ -245,6 +253,10 @@ class DreamOverlayServiceTest : SysuiTestCase() { ) } + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -287,6 +299,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { mKeyguardUpdateMonitor, mScrimManager, mCommunalInteractor, + kosmos.sceneInteractor, mSystemDialogsCloser, mUiEventLogger, mTouchInsetManager, @@ -768,6 +781,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB) + @DisableFlags(FLAG_SCENE_CONTAINER) @kotlin.Throws(RemoteException::class) fun testTransitionToGlanceableHub() = testScope.runTest { @@ -792,6 +806,35 @@ class DreamOverlayServiceTest : SysuiTestCase() { verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) } + @Test + @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB) + @kotlin.Throws(RemoteException::class) + fun testTransitionToGlanceableHub_sceneContainer() = + testScope.runTest { + // Inform the overlay service of dream starting. Do not show dream complications. + client.startDream( + mWindowParams, + mDreamOverlayCallback, + DREAM_COMPONENT, + false /*isPreview*/, + false, /*shouldShowComplication*/ + ) + mMainExecutor.runAllReady() + + verify(mDreamOverlayCallback).onRedirectWake(false) + clearInvocations(mDreamOverlayCallback) + kosmos.setCommunalAvailable(true) + mMainExecutor.runAllReady() + runCurrent() + verify(mDreamOverlayCallback).onRedirectWake(true) + client.onWakeRequested() + mMainExecutor.runAllReady() + runCurrent() + assertThat(kosmos.sceneContainerRepository.currentScene.value) + .isEqualTo(Scenes.Communal) + verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) + } + @Test @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB) @Throws(RemoteException::class) @@ -911,6 +954,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { // Verifies that the touch handling lifecycle is STARTED even if the dream starts while not // focused. @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun testLifecycle_dreamNotFocusedOnStart_isStarted() { val transitionState: MutableStateFlow = MutableStateFlow(ObservableTransitionState.Idle(CommunalScenes.Blank)) @@ -1024,6 +1068,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun testCommunalVisible_setsLifecycleState() { val client = client @@ -1060,6 +1105,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { // Verifies the dream's lifecycle @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun testLifecycleStarted_whenAnyOcclusion() { val client = client @@ -1256,5 +1302,11 @@ class DreamOverlayServiceTest : SysuiTestCase() { ComponentName("package", "homeControlPanel") private const val DREAM_COMPONENT = "package/dream" private const val WINDOW_NAME = "test" + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List { + return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer() + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..aacfaed1ab4a76905edc786bfc9d36a140f32430 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.dreams.ui.viewmodel + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.SceneFamilies +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class DreamUserActionsViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: DreamUserActionsViewModel + + @Before + fun setUp() { + underTest = kosmos.dreamUserActionsViewModel + underTest.activateIn(testScope) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun actions_singleShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isEmpty() + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = true, + shadeMode = ShadeMode.Single, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true)) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun actions_splitShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isEmpty() + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = true, + shadeMode = ShadeMode.Split, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun actions_dualShade() = + testScope.runTest { + val actions by collectLastValue(underTest.actions) + + setUpState( + isShadeTouchable = true, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Dual, + ) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo( + UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) + ) + + setUpState( + isShadeTouchable = false, + isDeviceUnlocked = false, + shadeMode = ShadeMode.Dual, + ) + assertThat(actions).isEmpty() + + setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual) + assertThat(actions).isNotEmpty() + assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home)) + assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone)) + assertThat(actions?.get(Swipe.Down)) + .isEqualTo( + UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true) + ) + } + + private fun TestScope.setUpState( + isShadeTouchable: Boolean, + isDeviceUnlocked: Boolean, + shadeMode: ShadeMode, + ) { + if (isShadeTouchable) { + kosmos.powerInteractor.setAwakeForTest() + } else { + kosmos.powerInteractor.setAsleepForTest() + } + + if (isDeviceUnlocked) { + unlockDevice() + } else { + lockDevice() + } + + if (shadeMode == ShadeMode.Dual) { + assertThat(DualShade.isEnabled).isTrue() + } else { + assertThat(DualShade.isEnabled).isFalse() + kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split) + } + runCurrent() + } + + private fun TestScope.lockDevice() { + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt index 5efb6171cdde05d85a1611074247c3d37408aa6a..f6a6e5465e1bc3c58d2c4794b6472bb7efb91cfb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt @@ -118,22 +118,6 @@ class TileHapticsViewModelTest : SysuiTestCase() { assertThat(msdlPlayer.latestPropertiesPlayed).isNull() } - @Test - fun onLongClick_whenTileDoesNotHandleLongClick_playsFailureHaptics() = - testScope.runTest { - // WHEN the tile is long-clicked but the tile does not handle a long-click - val state = QSTile.State().apply { handlesLongClick = false } - qsTile.changeState(state) - underTest.setTileInteractionState( - TileHapticsViewModel.TileInteractionState.LONG_CLICKED - ) - runCurrent() - - // THEN the failure token plays - assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) - assertThat(msdlPlayer.latestPropertiesPlayed).isNull() - } - @Test fun whenLaunchingFromClick_doesNotPlayHaptics() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt index 5df9b7b8d5b8d82230a9765df8d59d7df8d044ed..2efa2f34e3943ec54c045066822dfc704461b0bc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt @@ -85,63 +85,62 @@ class TutorialSchedulerInteractorTest : SysuiTestCase() { @Test fun connectKeyboard_delayElapse_launchForKeyboard() = testScope.runTest { - launchAndAssert(TutorialType.KEYBOARD) - keyboardRepository.setIsAnyKeyboardConnected(true) advanceTimeBy(LAUNCH_DELAY) + + launchAndAssert(TutorialType.KEYBOARD) } @Test fun connectBothDevices_delayElapse_launchForBoth() = testScope.runTest { - launchAndAssert(TutorialType.BOTH) - keyboardRepository.setIsAnyKeyboardConnected(true) touchpadRepository.setIsAnyTouchpadConnected(true) advanceTimeBy(LAUNCH_DELAY) + + launchAndAssert(TutorialType.BOTH) } @Test fun connectBothDevice_delayNotElapse_launchNothing() = testScope.runTest { - launchAndAssert(TutorialType.NONE) - keyboardRepository.setIsAnyKeyboardConnected(true) touchpadRepository.setIsAnyTouchpadConnected(true) advanceTimeBy(A_SHORT_PERIOD_OF_TIME) + + launchAndAssert(TutorialType.NONE) } @Test fun nothingConnect_delayElapse_launchNothing() = testScope.runTest { - launchAndAssert(TutorialType.NONE) - keyboardRepository.setIsAnyKeyboardConnected(false) touchpadRepository.setIsAnyTouchpadConnected(false) advanceTimeBy(LAUNCH_DELAY) + + launchAndAssert(TutorialType.NONE) } @Test fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() = testScope.runTest { - launchAndAssert(TutorialType.BOTH) - keyboardRepository.setIsAnyKeyboardConnected(true) advanceTimeBy(A_SHORT_PERIOD_OF_TIME) touchpadRepository.setIsAnyTouchpadConnected(true) advanceTimeBy(REMAINING_TIME) + + launchAndAssert(TutorialType.BOTH) } @Test fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() = testScope.runTest { - launchAndAssert(TutorialType.NONE) - keyboardRepository.setIsAnyKeyboardConnected(true) advanceTimeBy(A_SHORT_PERIOD_OF_TIME) touchpadRepository.setIsAnyTouchpadConnected(true) keyboardRepository.setIsAnyKeyboardConnected(false) advanceTimeBy(REMAINING_TIME) + launchAndAssert(TutorialType.NONE) } private suspend fun launchAndAssert(expectedTutorial: TutorialType) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cc4c7c419a11373146e3574658c3c699961f808e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyboard.shortcut.data.repository + +import android.content.Context +import android.content.Context.INPUT_SERVICE +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger +import android.hardware.input.KeyGestureEvent +import android.hardware.input.fakeInputManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.KeyEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES +import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { + + private val mockUserContext: Context = mock() + private val kosmos = + testKosmos().also { + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) + } + + private val fakeInputManager = kosmos.fakeInputManager + private val testScope = kosmos.testScope + private val helper = kosmos.shortcutHelperTestHelper + private val repo = kosmos.customShortcutCategoriesRepository + + @Before + fun setup() { + whenever(mockUserContext.getSystemService(INPUT_SERVICE)) + .thenReturn(fakeInputManager.inputManager) + } + + @Test + @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun categories_emitsCorrectlyConvertedShortcutCategories() { + testScope.runTest { + whenever( + fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) + ) + .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations) + + helper.toggle(deviceId = 123) + val categories by collectLastValue(repo.categories) + + assertThat(categories) + .containsExactlyElementsIn(expectedShortcutCategoriesWithSimpleShortcutCombination) + } + } + + @Test + @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun categories_emitsEmptyListWhenFlagIsDisabled() { + testScope.runTest { + whenever( + fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) + ) + .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations) + + helper.toggle(deviceId = 123) + val categories by collectLastValue(repo.categories) + + assertThat(categories).isEmpty() + } + } + + @Test + @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun categories_ignoresUnknownKeyGestureTypes() { + testScope.runTest { + whenever( + fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) + ) + .thenReturn(customizableInputGestureWithUnknownKeyGestureType) + + helper.toggle(deviceId = 123) + val categories by collectLastValue(repo.categories) + + assertThat(categories).isEmpty() + } + } + + private fun simpleInputGestureData( + keyCode: Int = KeyEvent.KEYCODE_A, + modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + keyGestureType: Int, + ): InputGestureData { + val builder = InputGestureData.Builder() + builder.setKeyGestureType(keyGestureType) + builder.setTrigger(createKeyTrigger(keyCode, modifiers)) + return builder.build() + } + + private fun simpleShortcutCategory( + category: ShortcutCategoryType, + subcategoryLabel: String, + shortcutLabel: String, + ): ShortcutCategory { + return ShortcutCategory( + type = category, + subCategories = + listOf( + ShortcutSubCategory( + label = subcategoryLabel, + shortcuts = listOf(simpleShortcut(shortcutLabel)), + ) + ), + ) + } + + private fun simpleShortcut(label: String) = + Shortcut( + label = label, + commands = + listOf( + ShortcutCommand( + isCustom = true, + keys = + listOf( + ShortcutKey.Text("Ctrl"), + ShortcutKey.Text("Alt"), + ShortcutKey.Text("A"), + ), + ) + ), + ) + + private val customizableInputGestureWithUnknownKeyGestureType = + // These key gesture events are currently not supported by shortcut helper customizer + listOf( + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY), + ) + + private val expectedShortcutCategoriesWithSimpleShortcutCombination = + listOf( + simpleShortcutCategory(System, "System apps", "Open assistant"), + simpleShortcutCategory(System, "System controls", "Go to home screen"), + simpleShortcutCategory(System, "System apps", "Open settings"), + simpleShortcutCategory(System, "System controls", "Lock screen"), + simpleShortcutCategory(System, "System controls", "View notifications"), + simpleShortcutCategory(System, "System apps", "Take a note"), + simpleShortcutCategory(System, "System controls", "Take screenshot"), + simpleShortcutCategory(System, "System controls", "Go back"), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch from split screen to full screen", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Use split screen with current app on the left", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch to app on left or above while using split screen", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Use split screen with current app on the right", + ), + simpleShortcutCategory( + MultiTasking, + "Split screen", + "Switch to app on right or below while using split screen", + ), + simpleShortcutCategory(System, "System controls", "Show shortcuts"), + simpleShortcutCategory(System, "System controls", "View recent apps"), + simpleShortcutCategory(AppCategories, "Applications", "Calculator"), + simpleShortcutCategory(AppCategories, "Applications", "Calendar"), + simpleShortcutCategory(AppCategories, "Applications", "Browser"), + simpleShortcutCategory(AppCategories, "Applications", "Contacts"), + simpleShortcutCategory(AppCategories, "Applications", "Email"), + simpleShortcutCategory(AppCategories, "Applications", "Maps"), + simpleShortcutCategory(AppCategories, "Applications", "SMS"), + simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"), + ) + + private val customizableInputGesturesWithSimpleShortcutCombinations = + listOf( + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + ), + simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING + ), + simpleInputGestureData( + keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER + ), + ) +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt similarity index 98% rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt index 620b8b63c71acf5e24bac8fd65ab07096efc8e1c..f90ab1fcc75b670e3223e7cb42bcc04012cbbb80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepositoryTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts +import com.android.systemui.keyboard.shortcut.defaultShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType @@ -47,7 +48,6 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource -import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource @@ -71,7 +71,7 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) -class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() { +class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() { private val fakeSystemSource = FakeKeyboardShortcutGroupsSource() private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource() @@ -87,7 +87,7 @@ class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() { it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() } - private val repo = kosmos.shortcutHelperCategoriesRepository + private val repo = kosmos.defaultShortcutCategoriesRepository private val helper = kosmos.shortcutHelperTestHelper private val testScope = kosmos.testScope private val fakeInputManager = kosmos.fakeInputManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 4e429c34bf2a372cf5cc99a5555825886956c85e..69fb03dc6433eab239ccd776f2dbe58bccb68dd5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -47,7 +47,8 @@ import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPR import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.res.R -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository @@ -99,7 +100,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { private lateinit var devicePostureRepository: FakeDevicePostureRepository private lateinit var facePropertyRepository: FakeFacePropertyRepository private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository - private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository + private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -142,7 +143,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 1, SensorStrength.STRONG, FingerprintSensorType.UDFPS_OPTICAL, - emptyMap() + emptyMap(), ) verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) verify(authController, times(2)).addCallback(authControllerCallback.capture()) @@ -247,7 +248,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { private fun deviceIsInPostureThatSupportsFaceAuth() { overrideResource( R.integer.config_face_auth_supported_posture, - DevicePostureController.DEVICE_POSTURE_FLIPPED + DevicePostureController.DEVICE_POSTURE_FLIPPED, ) devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED) } @@ -459,7 +460,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { biometricsAreEnabledBySettings() doNotDisableKeyguardAuthFeatures() - mobileConnectionsRepository.isAnySimSecure.value = false + mobileConnectionsRepository.fake.isAnySimSecure.value = false runCurrent() val isFaceAuthEnabledAndEnrolled by @@ -467,7 +468,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { assertThat(isFaceAuthEnabledAndEnrolled).isTrue() - mobileConnectionsRepository.isAnySimSecure.value = true + mobileConnectionsRepository.fake.isAnySimSecure.value = true runCurrent() assertThat(isFaceAuthEnabledAndEnrolled).isFalse() @@ -485,13 +486,13 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { doNotDisableKeyguardAuthFeatures() faceAuthIsStrongBiometric() biometricsAreEnabledBySettings() - mobileConnectionsRepository.isAnySimSecure.value = false + mobileConnectionsRepository.fake.isAnySimSecure.value = false onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) onNonStrongAuthChanged(false, PRIMARY_USER_ID) assertThat(isFaceAuthCurrentlyAllowed).isTrue() - mobileConnectionsRepository.isAnySimSecure.value = true + mobileConnectionsRepository.fake.isAnySimSecure.value = true assertThat(isFaceAuthCurrentlyAllowed).isFalse() } @@ -584,7 +585,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { testScope.runTest { overrideResource( R.integer.config_face_auth_supported_posture, - DevicePostureController.DEVICE_POSTURE_UNKNOWN + DevicePostureController.DEVICE_POSTURE_UNKNOWN, ) createBiometricSettingsRepository() @@ -597,7 +598,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { testScope.runTest { overrideResource( R.integer.config_face_auth_supported_posture, - DevicePostureController.DEVICE_POSTURE_FLIPPED + DevicePostureController.DEVICE_POSTURE_FLIPPED, ) createBiometricSettingsRepository() @@ -749,7 +750,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 1, SensorStrength.STRONG, FingerprintSensorType.UDFPS_OPTICAL, - emptyMap() + emptyMap(), ) // Non strong auth is not allowed now, FP is marked strong onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) @@ -761,7 +762,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 1, SensorStrength.CONVENIENCE, FingerprintSensorType.UDFPS_OPTICAL, - emptyMap() + emptyMap(), ) assertThat(isFingerprintCurrentlyAllowed).isFalse() @@ -769,7 +770,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 1, SensorStrength.WEAK, FingerprintSensorType.UDFPS_OPTICAL, - emptyMap() + emptyMap(), ) assertThat(isFingerprintCurrentlyAllowed).isFalse() } @@ -791,7 +792,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 1, SensorStrength.STRONG, FingerprintSensorType.UDFPS_OPTICAL, - emptyMap() + emptyMap(), ) // Non strong auth is not allowed now, FP is marked strong onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID) @@ -830,7 +831,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { UserInfo( /* id= */ PRIMARY_USER_ID, /* name= */ "primary user", - /* flags= */ UserInfo.FLAG_PRIMARY + /* flags= */ UserInfo.FLAG_PRIMARY, ) private const val ANOTHER_USER_ID = 1 @@ -838,7 +839,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { UserInfo( /* id= */ ANOTHER_USER_ID, /* name= */ "another user", - /* flags= */ UserInfo.FLAG_PRIMARY + /* flags= */ UserInfo.FLAG_PRIMARY, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt index 5a597fe8e92030b6c6b4d84f258adc3a15708e09..f537e32ac13d7dfc021d0987368f44a787b8e41e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt @@ -50,7 +50,6 @@ class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() { fakeSettings.userId = fakeUserTracker.userId underTest = KeyguardSmartspaceRepositoryImpl( - context = context, secureSettings = fakeSettings, userTracker = fakeUserTracker, applicationScope = scope.backgroundScope, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index bfe89de6229d61e1642c1f457e90c23c3e2a34d0..7a3089f332761c12a82eb788784ec8c15e14caa8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -16,11 +16,14 @@ package com.android.systemui.keyguard.data.repository +import android.animation.Animator import android.animation.ValueAnimator +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.app.animation.Interpolators +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.shared.model.KeyguardState @@ -41,6 +44,8 @@ import com.google.common.truth.Truth.assertThat import java.math.BigDecimal import java.math.RoundingMode import java.util.UUID +import kotlin.test.assertEquals +import kotlin.test.assertTrue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.dropWhile @@ -53,6 +58,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) @@ -65,6 +71,8 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardTransitionRepository private lateinit var runner: KeyguardTransitionRunner + private val animatorListener = mock() + @Before fun setUp() { underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main) @@ -80,7 +88,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { runner.startTransition( this, TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()), - maxFrames = 100 + maxFrames = 100, ) assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) @@ -107,7 +115,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { LOCKSCREEN, AOD, getAnimator(), - TransitionModeOnCanceled.LAST_VALUE + TransitionModeOnCanceled.LAST_VALUE, ), ) @@ -142,7 +150,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { LOCKSCREEN, AOD, getAnimator(), - TransitionModeOnCanceled.RESET + TransitionModeOnCanceled.RESET, ), ) @@ -177,7 +185,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { LOCKSCREEN, AOD, getAnimator(), - TransitionModeOnCanceled.REVERSE + TransitionModeOnCanceled.REVERSE, ), ) @@ -476,6 +484,72 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(steps.size).isEqualTo(3) } + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF) + fun forceFinishCurrentTransition_noFurtherStepsEmitted() = + testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) + + var sentForceFinish = false + + runner.startTransition( + this, + TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()), + maxFrames = 100, + // Force-finish on the second frame. + frameCallback = { frameNumber -> + if (!sentForceFinish && frameNumber > 1) { + testScope.launch { underTest.forceFinishCurrentTransition() } + sentForceFinish = true + } + }, + ) + + val lastTwoRunningSteps = + steps.filter { it.transitionState == TransitionState.RUNNING }.takeLast(2) + + // Make sure we stopped emitting RUNNING steps early, but then emitted a final 1f step. + assertTrue(lastTwoRunningSteps[0].value < 0.5f) + assertTrue(lastTwoRunningSteps[1].value == 1f) + + assertEquals(steps.last().from, AOD) + assertEquals(steps.last().to, LOCKSCREEN) + assertEquals(steps.last().transitionState, TransitionState.FINISHED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF) + fun forceFinishCurrentTransition_noTransitionStarted_noStepsEmitted() = + testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) + + underTest.forceFinishCurrentTransition() + assertEquals(0, steps.size) + } + + @Test + fun testForceFinishCurrentTransition_noTransitionRunning_unlocksMutex() = + testScope.runTest { + val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF }) + underTest.forceFinishCurrentTransition() + + assertThat(steps.isEmpty()) + + underTest.forceFinishCurrentTransition() + runCurrent() + + assertThat(steps.isEmpty()) + runner.startTransition( + this, + TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()), + maxFrames = 100, + ) + + advanceTimeBy(5000L) + + assertThat(steps.isNotEmpty()) + } + private fun listWithStep( step: BigDecimal, start: BigDecimal = BigDecimal.ZERO, @@ -505,7 +579,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { to, fractions[0].toFloat(), TransitionState.STARTED, - OWNER_NAME + OWNER_NAME, ) ) fractions.forEachIndexed { index, fraction -> @@ -519,7 +593,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { to, fraction.toFloat(), TransitionState.RUNNING, - OWNER_NAME + OWNER_NAME, ) ) } @@ -538,6 +612,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) setDuration(10) + addListener(animatorListener) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt index aee72de22a2471ac63e7addd9f20ceb040bf1088..28ac169e2bbe48c80725db24c87fe2ec26300c79 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt @@ -310,7 +310,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { // read during initialization to set up flows. Maybe there is a better way to handle that. underTest = KeyguardTouchHandlingInteractor( - appContext = mContext, + context = mContext, scope = testScope.backgroundScope, transitionInteractor = kosmos.keyguardTransitionInteractor, repository = keyguardRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index ecc62e908a4f3468b5c971f56c4a7cac11951be7..87ab3c89a6718eaf60507c29c429ba6ace48ab84 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -69,7 +69,9 @@ class ClockSectionTest : SysuiTestCase() { get() = kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() + context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) + - context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + context.resources.getDimensionPixelSize( + customR.dimen.keyguard_smartspace_top_offset + ) private val LARGE_CLOCK_TOP get() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt index 1abb441439fe0c0d6a697d65e37ea53ab51d9870..5798e0776c4fd019a96f1f6cb2bf3b85f65ea297 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt @@ -21,6 +21,7 @@ import android.animation.ValueAnimator import android.view.Choreographer.FrameCallback import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.TransitionInfo +import java.util.function.Consumer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -35,9 +36,8 @@ import org.junit.Assert.fail * Gives direct control over ValueAnimator, in order to make transition tests deterministic. See * [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly. */ -class KeyguardTransitionRunner( - val repository: KeyguardTransitionRepository, -) : AnimationFrameCallbackProvider { +class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) : + AnimationFrameCallbackProvider { private var frameCount = 1L private var frames = MutableStateFlow(Pair(0L, null)) @@ -48,7 +48,12 @@ class KeyguardTransitionRunner( * For transitions being directed by an animator. Will control the number of frames being * generated so the values are deterministic. */ - suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) { + suspend fun startTransition( + scope: CoroutineScope, + info: TransitionInfo, + maxFrames: Int = 100, + frameCallback: Consumer? = null, + ) { // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main // thread withContext(Dispatchers.Main) { @@ -62,7 +67,12 @@ class KeyguardTransitionRunner( isTerminated = frameNumber >= maxFrames if (!isTerminated) { - withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) } + try { + withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) } + frameCallback?.accept(frameNumber) + } catch (e: IllegalStateException) { + e.printStackTrace() + } } } } @@ -90,9 +100,13 @@ class KeyguardTransitionRunner( override fun postFrameCallback(cb: FrameCallback) { frames.value = Pair(frameCount++, cb) } + override fun postCommitCallback(runnable: Runnable) {} + override fun getFrameTime() = frameCount + override fun getFrameDelay() = 1L + override fun setFrameDelay(delay: Long) {} companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 93dede5a3dae3b9173c5bacffe633224b00160e8..f1f6c61a16e41f46e140dc7fdc3fe71efe9cb4a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -38,7 +38,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.res.R; import com.android.systemui.retail.data.repository.FakeRetailModeRepository; -import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl; +import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.utils.leaks.LeakCheckedTest; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSecurityFooterTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSecurityFooterTest.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 03feceb7c15ac0ed42c900e31ec5990dc733e81a..921a8a610c3730a30fe9e407e38be71c35dd1e16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.composefragment.viewmodel import android.app.StatusBarManager import android.content.testableContext +import android.graphics.Rect import android.testing.TestableLooper.RunWithLooper import androidx.compose.runtime.snapshots.Snapshot import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -40,9 +41,10 @@ import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow import com.android.systemui.res.R import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.android.systemui.statusbar.sysuiStatusBarStateController +import com.android.systemui.util.animation.DisappearParameters import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -375,6 +377,92 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun applyQsScrollPositionForClipping() = + with(kosmos) { + testScope.testWithinLifecycle { + val left = 1f + val top = 3f + val right = 5f + val bottom = 7f + + underTest.applyNewQsScrollerBounds(left, top, right, bottom) + + assertThat(qsMediaHost.currentClipping) + .isEqualTo(Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())) + } + } + + @Test + fun shouldUpdateMediaSquishiness_inSplitShadeFalse_mediaSquishinessSet() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest.isInSplitShade = false + underTest.squishinessFraction = 0.3f + + underTest.shouldUpdateSquishinessOnMedia = true + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f) + + underTest.shouldUpdateSquishinessOnMedia = false + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f) + } + } + + @Test + fun inSplitShade_differentStatusBarState_mediaSquishinessSet() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest.isInSplitShade = true + underTest.squishinessFraction = 0.3f + + sysuiStatusBarStateController.setState(StatusBarState.SHADE) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f) + + sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD) + runCurrent() + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f) + + sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED) + runCurrent() + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f) + } + } + + @Test + fun disappearParams() = + with(kosmos) { + testScope.testWithinLifecycle { + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(false) + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qqsMediaHost.disappearParameters) + .isEqualTo(disappearParamsColumn) + assertThat(underTest.qsMediaHost.disappearParameters) + .isEqualTo(disappearParamsColumn) + + setConfigurationForMediaInRow(true) + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow) + assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow) + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA @@ -404,6 +492,26 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } private const val epsilon = 0.001f + + private val disappearParamsColumn = + DisappearParameters().apply { + fadeStartPosition = 0.95f + disappearStart = 0f + disappearEnd = 0.95f + disappearSize.set(1f, 0f) + gonePivot.set(0f, 0f) + contentTranslationFraction.set(0f, 1f) + } + + private val disappearParamsRow = + DisappearParameters().apply { + fadeStartPosition = 0.95f + disappearStart = 0f + disappearEnd = 0.6f + disappearSize.set(0f, 0.4f) + gonePivot.set(1f, 0f) + contentTranslationFraction.set(0.25f, 1f) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt index 3910903af4aa108586a79c0bbe7e54b702b8a2ab..ae7c44e9b146320a9af43dee7365bd412e41eae3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt @@ -35,7 +35,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class EditTileListStateTest : SysuiTestCase() { - private val underTest = EditTileListState(TestEditTiles, 4) + private val underTest = EditTileListState(TestEditTiles, columns = 4, largeTilesSpan = 2) @Test fun startDrag_listHasSpacers() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt index 6fce108f35a1c40d43a63728a6d0eef686101fac..ee2a1d56937b33d19ab348d4539ec6929203ae24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt @@ -19,10 +19,10 @@ package com.android.systemui.qs.tileimpl import android.animation.AnimatorTestRule import android.content.Context import android.service.quicksettings.Tile -import android.testing.AndroidTestingRunner import android.view.ContextThemeWrapper import android.view.View import android.widget.ImageView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,7 +35,7 @@ import org.junit.Rule import org.junit.runner.RunWith /** Test for regression b/311121830 and b/323125376 */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @UiThreadTest @SmallTest class QSIconViewImplTest_311121830 : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt index ba7a65dd8e65bdb680cce4990407694f9e2d0b12..47bfda41c7ad9b964b38f65f83f63be51bcf575b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt index b5365206775589770ea7a3f30efc7ae596c2e7e7..b47dcb567135e25a43e71e73fca6fd012b0eacc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.retail.data.repository.FakeRetailModeRepository +import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt index bf97afed92f4f4ea7613d8e925c5b045743a050d..959081663b56485c1baf2db9182e4d3c9d8e3363 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt @@ -27,6 +27,7 @@ import com.android.compose.animation.scene.ObservableTransitionState.Transition. import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -194,6 +195,24 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { .isFalse() } + @Test + fun invisibleDueToOcclusion_isDreaming_emitsTrue() = + testScope.runTest { + val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) + + // Verify that we start with unoccluded + assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse() + + // Start dreaming, which is an occluding activity + showOccludingActivity() + kosmos.keyguardInteractor.setDreaming(true) + + // Verify not invisible when dreaming + assertWithMessage("Should be invisible when dreaming") + .that(invisibleDueToOcclusion) + .isTrue() + } + /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */ private fun TestScope.showOccludingActivity() { keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 152911a30524bc1645a6e270c2e517c79317853f..2e074da02103ff4921551d02c83e90537850ef94 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector @@ -54,6 +55,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer @@ -71,6 +73,7 @@ import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.keyguard.dismissCallbackRegistry +import com.android.systemui.keyguard.domain.interactor.dozeInteractor import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor @@ -114,6 +117,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -140,6 +144,8 @@ class SceneContainerStartableTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val deviceEntryHapticsInteractor by lazy { kosmos.deviceEntryHapticsInteractor } + private val dozeInteractor by lazy { kosmos.dozeInteractor } + private val keyguardInteractor by lazy { kosmos.keyguardInteractor } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor } private val bouncerInteractor by lazy { kosmos.bouncerInteractor } @@ -369,6 +375,64 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isVisible).isTrue() } + @Test + fun hydrateVisibility_whileDreaming() = + testScope.runTest { + val isVisible by collectLastValue(sceneInteractor.isVisible) + + // GIVEN the device is dreaming + val transitionState = + prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Dream) + underTest.start() + assertThat(isVisible).isFalse() + } + + @Test + fun hydrateVisibility_onCommunalWhileOccluded() = + testScope.runTest { + val isVisible by collectLastValue(sceneInteractor.isVisible) + + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + true, + mock(), + ) + prepareState(isDeviceUnlocked = false, initialSceneKey = Scenes.Communal) + underTest.start() + runCurrent() + assertThat(isVisible).isTrue() + } + + @Test + fun hydrateVisibility_inCommunalTransition() = + testScope.runTest { + val isVisible by collectLastValue(sceneInteractor.isVisible) + + // GIVEN the device is dreaming + val transitionState = + prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + initialSceneKey = Scenes.Dream, + ) + underTest.start() + assertThat(isVisible).isFalse() + + // WHEN a transition starts to the communal hub + sceneInteractor.changeScene(Scenes.Dream, "switching to dream for test") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Dream, + toScene = Scenes.Communal, + currentScene = flowOf(Scenes.Dream), + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + runCurrent() + // THEN scenes are visible + assertThat(isVisible).isTrue() + } + @Test fun startsInLockscreenScene() = testScope.runTest { @@ -640,7 +704,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fun switchToAOD_whenAvailable_whenDeviceSleepsLocked() = testScope.runTest { kosmos.lockscreenSceneTransitionInteractor.start() - val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState) + val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState) val currentTransitionInfo by collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal) val transitionState = @@ -670,7 +734,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fun switchToDozing_whenAodUnavailable_whenDeviceSleepsLocked() = testScope.runTest { kosmos.lockscreenSceneTransitionInteractor.start() - val asleepState by collectLastValue(kosmos.keyguardInteractor.asleepKeyguardState) + val asleepState by collectLastValue(keyguardInteractor.asleepKeyguardState) val currentTransitionInfo by collectLastValue(kosmos.keyguardTransitionRepository.currentTransitionInfoInternal) val transitionState = @@ -2356,6 +2420,118 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isLockscreenEnabled).isTrue() } + @Test + fun stayOnLockscreen_whenDozingStarted() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + prepareState() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + underTest.start() + + // Stay on Lockscreen when dozing and dreaming + dozeInteractor.setIsDozing(true) + keyguardInteractor.setDreaming(true) + kosmos.fakeKeyguardRepository.setDreamingWithOverlay(false) + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun switchFromLockscreenToDream_whenDreamStarted() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + prepareState() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + underTest.start() + + powerInteractor.setAwakeForTest() + keyguardInteractor.setDreaming(true) + // Move past initial delay with [KeyguardInteractor#isAbleToDream] + advanceTimeBy(600L) + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Dream) + } + + @Test + fun switchFromDreamToLockscreen_whenLockedAndDreamStopped() = + testScope.runTest { + keyguardInteractor.setDreaming(true) + val currentScene by collectLastValue(sceneInteractor.currentScene) + prepareState(initialSceneKey = Scenes.Dream) + assertThat(currentScene).isEqualTo(Scenes.Dream) + underTest.start() + + keyguardInteractor.setDreaming(false) + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun switchFromDreamToGone_whenUnlockedAndDreamStopped() = + testScope.runTest { + keyguardInteractor.setDreaming(true) + val currentScene by collectLastValue(sceneInteractor.currentScene) + prepareState(initialSceneKey = Scenes.Dream, isDeviceUnlocked = true) + assertThat(currentScene).isEqualTo(Scenes.Dream) + underTest.start() + + keyguardInteractor.setDreaming(false) + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Gone) + } + + @Test + fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() = + testScope.runTest { + val transitionState = + prepareState( + isDeviceUnlocked = false, + initialSceneKey = Scenes.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + ) + underTest.start() + + val isUnlocked by + collectLastValue( + kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked } + ) + val currentScene by collectLastValue(sceneInteractor.currentScene) + val backStack by collectLastValue(sceneBackInteractor.backStack) + val isAlternateBouncerVisible by + collectLastValue(kosmos.alternateBouncerInteractor.isVisible) + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isFalse() + + // Change to shade. + sceneInteractor.changeScene(Scenes.Shade, "") + transitionState.value = ObservableTransitionState.Idle(Scenes.Shade) + runCurrent() + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isFalse() + + // Show the alternate bouncer. + kosmos.alternateBouncerInteractor.forceShow() + kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open + runCurrent() + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isTrue() + + // Trigger a fingerprint unlock. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(isUnlocked).isTrue() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone) + assertThat(isAlternateBouncerVisible).isFalse() + } + private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow, toScene: SceneKey, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt similarity index 97% rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt index a10d81f86d8eae63073bab8bac4d61aed2d28827..1413204f7fed7a1e435e5d117cc3d7d04b58cb25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt @@ -20,8 +20,8 @@ import android.app.PendingIntent import android.content.Intent import android.os.Bundle import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.view.Window +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.argumentCaptor @@ -39,7 +39,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.verify import org.mockito.kotlin.verifyBlocking -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ActionExecutorTest : SysuiTestCase() { private val scheduler = TestCoroutineScheduler() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt index c5070286802562b45da5459d956e6a9640b6df39..84b7d10c42a484aee15556ca5e64c4bf415d2545 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt @@ -21,10 +21,10 @@ import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.MATCH_ANY_USER import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS -import android.testing.AndroidTestingRunner import android.view.Display import android.view.IWindowManager import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat @@ -43,7 +43,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ScreenshotDetectionControllerTest { @Mock lateinit var windowManager: IWindowManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 77b5c9115295f704c01cd6fc79b8f252396ee4eb..d2eca0d95a30c9d5412383d6ff94702d7adedfd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -23,9 +23,9 @@ import android.content.ComponentName import android.net.Uri import android.os.UserHandle import android.os.UserManager -import android.testing.AndroidTestingRunner import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotRequest import com.android.systemui.SysuiTestCase @@ -49,7 +49,7 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TakeScreenshotServiceTest : SysuiTestCase() { private val userManager = mock { on { isUserUnlocked } doReturn (true) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 01c17bd6285c9039f5fa7184cd6154b838503086..94a19c80dc006b37e73b695e4a076108e7a2c9ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -148,7 +148,6 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.HeadsUpTouchHelper; @@ -428,7 +427,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), mKosmos.getDeviceProvisioningInteractor(), - new FakeDisableFlagsRepository(), + mKosmos.getDisableFlagsInteractor(), mDozeParameters, mFakeKeyguardRepository, mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 443595dbdf77d437f67564fb3b76814ce5bc50f8..ef132d5a498934da7b2f77a1ffbb98e01d60b3c7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -149,7 +149,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { @Mock protected LargeScreenHeaderHelper mLargeScreenHeaderHelper; protected FakeDisableFlagsRepository mDisableFlagsRepository = - new FakeDisableFlagsRepository(); + mKosmos.getFakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); protected FakeShadeRepository mShadeRepository = new FakeShadeRepository(); @@ -185,7 +185,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), mKosmos.getDeviceProvisioningInteractor(), - mDisableFlagsRepository, + mKosmos.getDisableFlagsInteractor(), mDozeParameters, mKeyguardRepository, keyguardTransitionInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt index 46961d4db0f6ac065870f4f3c2436d66ba467d6e..ee3f8016c4106598d232d26f4bf2b4fd0b531e88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade import android.app.StatusBarManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancelChildren @@ -61,7 +61,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp mDisableFlagsRepository.disableFlags.value = DisableFlagsModel( StatusBarManager.DISABLE_NONE, - StatusBarManager.DISABLE2_QUICK_SETTINGS + StatusBarManager.DISABLE2_QUICK_SETTINGS, ) runCurrent() @@ -76,7 +76,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp mDisableFlagsRepository.disableFlags.value = DisableFlagsModel( StatusBarManager.DISABLE_NONE, - StatusBarManager.DISABLE2_NOTIFICATION_SHADE + StatusBarManager.DISABLE2_NOTIFICATION_SHADE, ) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java index 9a8df33a0276a5ff413d0cc59903b496e5e1b347..cd55bb23c1fcef42ac161e6905c5e6d1727739b7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java @@ -38,11 +38,11 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.CarrierTextManager; @@ -76,7 +76,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt similarity index 95% rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt index a9a5cac6112eb5cd932abde4da60bf3bf14834da..4e7839efe2a387a477a96e282806da7365184716 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt @@ -34,13 +34,13 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -class ShadePositionRepositoryTest : SysuiTestCase() { +class ShadeDisplaysRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandRegistry = kosmos.commandRegistry private val pw = PrintWriter(StringWriter()) - private val underTest = ShadePositionRepositoryImpl(commandRegistry) + private val underTest = ShadeDisplaysRepositoryImpl(commandRegistry) @Before fun setUp() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8ef1e568cd5810eb72859585102abec1b523ef53 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.shade.domain.interactor + +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.view.Display +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository +import com.android.systemui.display.shared.model.DisplayWindowProperties +import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository +import com.android.systemui.statusbar.phone.ConfigurationForwarder +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@SmallTest +class ShadeDisplaysInteractorTest : SysuiTestCase() { + + private val shadeRootview = mock() + private val positionRepository = FakeShadeDisplayRepository() + private val defaultContext = mock() + private val secondaryContext = mock() + private val contextStore = FakeDisplayWindowPropertiesRepository() + private val testScope = TestScope(UnconfinedTestDispatcher()) + private val configurationForwarder = mock() + private val defaultWm = mock() + private val secondaryWm = mock() + private val resources = mock() + private val configuration = mock() + private val display = mock() + + private val interactor = + ShadeDisplaysInteractor( + shadeRootview, + positionRepository, + defaultContext, + contextStore, + testScope, + configurationForwarder, + testScope.coroutineContext, + ) + + @Before + fun setup() { + whenever(shadeRootview.display).thenReturn(display) + whenever(display.displayId).thenReturn(0) + + whenever(resources.configuration).thenReturn(configuration) + whenever(resources.configuration).thenReturn(configuration) + + whenever(defaultContext.displayId).thenReturn(0) + whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm) + whenever(defaultContext.resources).thenReturn(resources) + contextStore.insert( + DisplayWindowProperties( + displayId = 0, + windowType = TYPE_NOTIFICATION_SHADE, + context = defaultContext, + windowManager = defaultWm, + layoutInflater = mock(), + ) + ) + + whenever(secondaryContext.displayId).thenReturn(1) + whenever(secondaryContext.getSystemService(any())).thenReturn(secondaryWm) + whenever(secondaryContext.resources).thenReturn(resources) + contextStore.insert( + DisplayWindowProperties( + displayId = 1, + windowType = TYPE_NOTIFICATION_SHADE, + context = secondaryContext, + windowManager = secondaryWm, + layoutInflater = mock(), + ) + ) + } + + @Test + fun start_shadeInCorrectPosition_notAddedOrRemoved() { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(0) + interactor.start() + testScope.advanceUntilIdle() + + verifyNoMoreInteractions(defaultWm) + verifyNoMoreInteractions(secondaryWm) + } + + @Test + fun start_shadeInWrongPosition_changes() { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) + interactor.start() + testScope.advanceUntilIdle() + + verify(defaultWm).removeView(eq(shadeRootview)) + verify(secondaryWm).addView(eq(shadeRootview), any()) + } + + @Test + fun start_shadePositionChanges_removedThenAdded() { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(0) + interactor.start() + testScope.advanceUntilIdle() + + positionRepository.setDisplayId(1) + testScope.advanceUntilIdle() + + verify(defaultWm).removeView(eq(shadeRootview)) + verify(secondaryWm).addView(eq(shadeRootview), any()) + } + + @Test + fun start_shadePositionChanges_newConfigPropagated() { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(0) + interactor.start() + testScope.advanceUntilIdle() + + positionRepository.setDisplayId(1) + testScope.advanceUntilIdle() + + verify(configurationForwarder).onConfigurationChanged(eq(configuration)) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index 19ac0cf401607c454011340690f09db466b33e2a..da652c4e1b5e0696f7b5b50710c3d3ba69384f6e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -37,8 +37,8 @@ import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.shade.shadeTestUtil -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.android.systemui.statusbar.phone.dozeParameters import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt index 907c68440b557c452cc4b5d2bf8a144b1b16d0e9..cd078211f934583aa063d24e69e8d5f29b6e394e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.log.LogBufferFactory import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.disableflags.DisableFlagsLogger -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java index c8ef663a4f81c8bd38bfb38a31ae28e4c10d5a79..e974c2e70f68c73cc4f964baedacf8199cee27d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java @@ -30,13 +30,13 @@ import static org.mockito.Mockito.verify; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.testing.AndroidTestingRunner; import android.util.FloatProperty; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.app.animation.Interpolators; @@ -51,7 +51,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class PropertyAnimatorTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index e21a005b8ada7c1282bf0e2db304c2408e0ae96e..4ef9792a2ad948f1cbaa54003a764a85b83fe596 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -68,12 +68,12 @@ import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; import android.util.ArraySet; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -118,7 +118,7 @@ import java.util.List; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotifCollectionTest extends SysuiTestCase { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 9613f76c2b48b05cc9dde9474c669c92f8086fdc..2c488e3a7242e416879cb9b2c23bcd70ccee9c74 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips -import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -52,6 +51,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback import com.android.systemui.statusbar.phone.NotificationGroupTestHelper +import com.android.systemui.statusbar.policy.BaseHeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor @@ -101,7 +101,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val notifPipeline: NotifPipeline = mock() private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true) - private val headsUpManager: HeadsUpManagerPhone = mock() + private val headsUpManager: BaseHeadsUpManager = mock() private val headsUpViewBinder: HeadsUpViewBinder = mock() private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock() private val remoteInputManager: NotificationRemoteInputManager = mock() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index ea5c29ef30aa25995f337c41d4bc3e6547eeaab7..3ad41a54ac7e16555e9667fbe21b5774988d0747 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -32,9 +34,9 @@ import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.compose.animation.scene.ObservableTransitionState; @@ -42,6 +44,7 @@ import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.BrokenWithSceneContainer; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -78,14 +81,23 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; +import java.util.List; + import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.test.TestScope; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class VisualStabilityCoordinatorTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List getParams() { + return parameterizeSceneContainerFlag(); + } + private VisualStabilityCoordinator mCoordinator; @Mock private DumpManager mDumpManager; @@ -117,6 +129,11 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { private NotificationEntry mEntry; private GroupEntry mGroupEntry; + public VisualStabilityCoordinatorTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -251,6 +268,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } @Test + @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() { // GIVEN the panel true expanded and device isn't pulsing setFullyDozed(false); @@ -267,6 +285,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } @Test + @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() { // GIVEN the panel true expanded and device isn't pulsing setFullyDozed(false); @@ -520,6 +539,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Test @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION) + @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer public void testNotLockscreenInGoneTransition_invalidationCalled() { // GIVEN visual stability is being maintained b/c animation is playing mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava( @@ -589,6 +609,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } @Test + @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer public void testCommunalShowingWillNotSuppressReordering() { // GIVEN panel is expanded, communal is showing, and QS is collapsed setPulsing(false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt index ca75ca679c319c392e1f2b77d3ad265b48a0e6e3..a70d24efada74dfcbac90e632e2826584e2e62d4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -28,43 +28,41 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.never -import org.mockito.Mockito.spy -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.inOrder +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest @RunWith(AndroidJUnit4::class) class RenderStageManagerTest : SysuiTestCase() { - @Mock private lateinit var shadeListBuilder: ShadeListBuilder - @Mock private lateinit var onAfterRenderListListener: OnAfterRenderListListener - @Mock private lateinit var onAfterRenderGroupListener: OnAfterRenderGroupListener - @Mock private lateinit var onAfterRenderEntryListener: OnAfterRenderEntryListener + private val shadeListBuilder: ShadeListBuilder = mock() + private val onAfterRenderListListener: OnAfterRenderListListener = mock() + private val onAfterRenderGroupListener: OnAfterRenderGroupListener = mock() + private val onAfterRenderEntryListener: OnAfterRenderEntryListener = mock() + private val spyViewRenderer = spy(FakeNotifViewRenderer()) private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener + private lateinit var renderStageManager: RenderStageManager - private val spyViewRenderer = spy(FakeNotifViewRenderer()) @Before fun setUp() { - MockitoAnnotations.initMocks(this) - renderStageManager = RenderStageManager() renderStageManager.attach(shadeListBuilder) - onRenderListListener = withArgCaptor { - verify(shadeListBuilder).setOnRenderListListener(capture()) - } + + val captor = argumentCaptor() + verify(shadeListBuilder).setOnRenderListListener(captor.capture()) + onRenderListListener = captor.lastValue } private fun setUpRenderer() { @@ -89,7 +87,7 @@ class RenderStageManagerTest : SysuiTestCase() { verifyNoMoreInteractions( onAfterRenderListListener, onAfterRenderGroupListener, - onAfterRenderEntryListener + onAfterRenderEntryListener, ) } @@ -171,7 +169,7 @@ class RenderStageManagerTest : SysuiTestCase() { verifyNoMoreInteractions( onAfterRenderListListener, onAfterRenderGroupListener, - onAfterRenderEntryListener + onAfterRenderEntryListener, ) } @@ -191,30 +189,27 @@ class RenderStageManagerTest : SysuiTestCase() { verifyNoMoreInteractions( onAfterRenderListListener, onAfterRenderGroupListener, - onAfterRenderEntryListener + onAfterRenderEntryListener, ) } - private fun listWith2Groups8Entries() = listOf( - group( - notif(1), - notif(2), - notif(3) - ), - notif(4), - group( - notif(5), - notif(6), - notif(7) - ), - notif(8) - ) + private fun listWith2Groups8Entries() = + listOf( + group(notif(1), notif(2), notif(3)), + notif(4), + group(notif(5), notif(6), notif(7)), + notif(8), + ) private class FakeNotifViewRenderer : NotifViewRenderer { override fun onRenderList(notifList: List) {} + override fun getStackController(): NotifStackController = mock() + override fun getGroupController(group: GroupEntry): NotifGroupController = mock() + override fun getRowController(entry: NotificationEntry): NotifRowController = mock() + override fun onDispatchComplete() {} } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt index 79ff4be253e176a1285afd4b8559d7452a1951ac..7eac7e86e372550db1003836f161d6343a337642 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt @@ -21,8 +21,8 @@ import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component @@ -51,10 +51,7 @@ class NotificationAlertsInteractorTest : SysuiTestCase() { fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() = with(testComponent) { disableFlags.disableFlags.value = - DisableFlagsModel( - StatusBarManager.DISABLE_NONE, - StatusBarManager.DISABLE2_NONE, - ) + DisableFlagsModel(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE) assertThat(underTest.areNotificationAlertsEnabled()).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt index fd4192151c57cb3f19fbbc183599a57f3ff9da22..371e1c569cefd8d2cbda9dd345dc90b9025cc30a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt @@ -16,13 +16,13 @@ package com.android.systemui.statusbar.notification.row import android.content.Context -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.AttributeSet import android.view.View import android.widget.Button import android.widget.FrameLayout import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED @@ -38,7 +38,7 @@ import org.mockito.Mockito.verify /** Tests for [NotifLayoutInflaterFactory] */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifLayoutInflaterFactoryTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 3669e3d8fed18c7b9889d9ba7a87df202310e16f..b8745b3e95b2393f22ebd7cf3d18f64937b40dc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -21,12 +21,12 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class NotificationSectionsManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index b2a485c48860054dfa90fa9bd358035a37f34601..b877456ab604c294733337f95acb18135c13bd62 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -32,23 +32,19 @@ import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.AvalancheController -import com.android.systemui.util.mockito.mock import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat -import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertFalse -import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assume import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.any -import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -846,7 +842,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { val viewStart = 0f val shelfStart = 1f - val expandableView = mock(ExpandableView::class.java) + val expandableView = mock() whenever(expandableView.isExpandAnimationRunning).thenReturn(false) whenever(expandableView.hasExpandingChild()).thenReturn(false) @@ -854,7 +850,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState.yTranslation = viewStart stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart) - assertFalse(expandableViewState.hidden) + assertThat(expandableViewState.hidden).isFalse() } @Test @@ -862,7 +858,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { val shelfStart = 0f val viewStart = 1f - val expandableView = mock(ExpandableView::class.java) + val expandableView = mock() whenever(expandableView.isExpandAnimationRunning).thenReturn(false) whenever(expandableView.hasExpandingChild()).thenReturn(false) @@ -870,7 +866,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState.yTranslation = viewStart stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart) - assertTrue(expandableViewState.hidden) + assertThat(expandableViewState.hidden).isTrue() } @Test @@ -878,7 +874,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { val shelfStart = 0f val viewStart = 1f - val expandableView = mock(ExpandableView::class.java) + val expandableView = mock() whenever(expandableView.isExpandAnimationRunning).thenReturn(true) whenever(expandableView.hasExpandingChild()).thenReturn(true) @@ -886,7 +882,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState.yTranslation = viewStart stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart) - assertFalse(expandableViewState.hidden) + assertThat(expandableViewState.hidden).isFalse() } @Test @@ -898,12 +894,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState, /* isShadeExpanded= */ true, /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, + /* topVisible = */ true, /* viewEnd= */ 0f, - /* maxHunY= */ 10f, + /* hunMax = */ 10f, ) - assertTrue(expandableViewState.headsUpIsVisible) + assertThat(expandableViewState.headsUpIsVisible).isTrue() } @Test @@ -915,12 +911,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState, /* isShadeExpanded= */ true, /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, + /* topVisible = */ true, /* viewEnd= */ 10f, - /* maxHunY= */ 0f, + /* hunMax = */ 0f, ) - assertFalse(expandableViewState.headsUpIsVisible) + assertThat(expandableViewState.headsUpIsVisible).isFalse() } @Test @@ -932,12 +928,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState, /* isShadeExpanded= */ false, /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ true, + /* topVisible = */ true, /* viewEnd= */ 10f, - /* maxHunY= */ 1f, + /* hunMax = */ 1f, ) - assertTrue(expandableViewState.headsUpIsVisible) + assertThat(expandableViewState.headsUpIsVisible).isTrue() } @Test @@ -949,12 +945,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState, /* isShadeExpanded= */ true, /* mustStayOnScreen= */ false, - /* isViewEndVisible= */ true, + /* topVisible = */ true, /* viewEnd= */ 10f, - /* maxHunY= */ 1f, + /* hunMax = */ 1f, ) - assertTrue(expandableViewState.headsUpIsVisible) + assertThat(expandableViewState.headsUpIsVisible).isTrue() } @Test @@ -966,12 +962,12 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { expandableViewState, /* isShadeExpanded= */ true, /* mustStayOnScreen= */ true, - /* isViewEndVisible= */ false, + /* topVisible = */ false, /* viewEnd= */ 10f, - /* maxHunY= */ 1f, + /* hunMax = */ 1f, ) - assertTrue(expandableViewState.headsUpIsVisible) + assertThat(expandableViewState.headsUpIsVisible).isTrue() } @Test @@ -986,7 +982,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { ) // qqs (10 + 0) < viewY (50) - assertEquals(50f, expandableViewState.yTranslation) + assertThat(expandableViewState.yTranslation).isEqualTo(50f) } @Test @@ -1001,7 +997,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { ) // qqs (10 + 0) > viewY (-10) - assertEquals(10f, expandableViewState.yTranslation) + assertThat(expandableViewState.yTranslation).isEqualTo(10f) } @Test @@ -1019,7 +1015,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { // newTranslation = max(10, -100) = 10 // distToRealY = 10 - (-100f) = 110 // height = max(20 - 110, 10f) - assertEquals(10, expandableViewState.height) + assertThat(expandableViewState.height).isEqualTo(10) } @Test @@ -1037,7 +1033,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { // newTranslation = max(10, 5) = 10 // distToRealY = 10 - 5 = 5 // height = max(20 - 5, 10) = 15 - assertEquals(15, expandableViewState.height) + assertThat(expandableViewState.height).isEqualTo(15) } @Test @@ -1047,9 +1043,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { /* hostViewHeight= */ 100f, /* stackY= */ 110f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f, + /* originalCornerRadius = */ 0f, ) - assertEquals(1f, currentRoundness) + assertThat(currentRoundness).isEqualTo(1f) } @Test @@ -1059,9 +1055,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { /* hostViewHeight= */ 100f, /* stackY= */ 90f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f, + /* originalCornerRadius = */ 0f, ) - assertEquals(0.5f, currentRoundness) + assertThat(currentRoundness).isEqualTo(0.5f) } @Test @@ -1071,9 +1067,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { /* hostViewHeight= */ 100f, /* stackY= */ 0f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 0f, + /* originalCornerRadius = */ 0f, ) - assertEquals(0f, currentRoundness) + assertThat(currentRoundness).isZero() } @Test @@ -1083,9 +1079,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { /* hostViewHeight= */ 100f, /* stackY= */ 0f, /* viewMaxHeight= */ 20f, - /* originalCornerRoundness= */ 1f, + /* originalCornerRadius = */ 1f, ) - assertEquals(1f, currentRoundness) + assertThat(currentRoundness).isEqualTo(1f) } @Test @@ -1105,13 +1101,14 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updateChildZValue( /* i= */ 0, /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, + /* algorithmState = */ algorithmState, /* ambientState= */ ambientState, - /* shouldElevateHun= */ true, + /* isTopHun = */ true, ) // Then: full shadow would be applied - assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + assertThat(childHunView.viewState.zTranslation) + .isEqualTo(px(R.dimen.heads_up_pinned_elevation)) } @Test @@ -1133,9 +1130,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updateChildZValue( /* i= */ 0, /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, + /* algorithmState = */ algorithmState, /* ambientState= */ ambientState, - /* shouldElevateHun= */ true, + /* isTopHun = */ true, ) // Then: HUN should have shadow, but not as full size @@ -1166,13 +1163,13 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updateChildZValue( /* i= */ 0, /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, + /* algorithmState = */ algorithmState, /* ambientState= */ ambientState, - /* shouldElevateHun= */ true, + /* isTopHun = */ true, ) // Then: HUN should not have shadow - assertEquals(0f, childHunView.viewState.zTranslation) + assertThat(childHunView.viewState.zTranslation).isZero() } @Test @@ -1195,13 +1192,14 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updateChildZValue( /* i= */ 0, /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, + /* algorithmState = */ algorithmState, /* ambientState= */ ambientState, - /* shouldElevateHun= */ true, + /* isTopHun = */ true, ) // Then: HUN should have full shadow - assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + assertThat(childHunView.viewState.zTranslation) + .isEqualTo(px(R.dimen.heads_up_pinned_elevation)) } @Test @@ -1225,9 +1223,9 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updateChildZValue( /* i= */ 0, /* childrenOnTop= */ 0.0f, - /* StackScrollAlgorithmState= */ algorithmState, + /* algorithmState = */ algorithmState, /* ambientState= */ ambientState, - /* shouldElevateHun= */ true, + /* isTopHun = */ true, ) // Then: HUN should have shadow, but not as full size @@ -1251,7 +1249,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState) // Then: ambientState.pulsingRow should still be pulsingNotificationView - assertTrue(ambientState.isPulsingRow(pulsingNotificationView)) + assertThat(ambientState.isPulsingRow(pulsingNotificationView)).isTrue() } @Test @@ -1268,7 +1266,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState) // Then: ambientState.pulsingRow should record the pulsingNotificationView - assertTrue(ambientState.isPulsingRow(pulsingNotificationView)) + assertThat(ambientState.isPulsingRow(pulsingNotificationView)).isTrue() } @Test @@ -1287,7 +1285,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState) // Then: ambientState.pulsingRow should be null - assertTrue(ambientState.isPulsingRow(null)) + assertThat(ambientState.isPulsingRow(null)).isTrue() } @Test @@ -1310,10 +1308,8 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, 0) // Then: pulsingNotificationView should show at full height - assertEquals( - stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView), - pulsingNotificationView.viewState.height, - ) + assertThat(pulsingNotificationView.viewState.height) + .isEqualTo(stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView)) // After: reset dozeAmount and expansionFraction ambientState.dozeAmount = 0f @@ -1418,7 +1414,7 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max } - assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) + assertThat(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)).isTrue() } @Test @@ -1431,7 +1427,8 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { ambientState.maxHeadsUpTranslation - height - 1 // move it below the max } - assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) + assertThat(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)) + .isFalse() } // endregion @@ -1579,13 +1576,13 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { } private fun mockExpandableNotificationRow(): ExpandableNotificationRow { - return mock(ExpandableNotificationRow::class.java).apply { + return mock().apply { whenever(viewState).thenReturn(ExpandableViewState()) } } private fun mockFooterView(height: Int): FooterView { - return mock(FooterView::class.java).apply { + return mock().apply { whenever(viewState).thenReturn(FooterViewState()) whenever(intrinsicHeight).thenReturn(height) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index a862fdfeca2233655648320ea86b3919216e4e97..7a51b2ddb020c24425610243b99d26b299f17c63 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone import android.content.Context import android.content.res.Configuration import android.graphics.Rect +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.Display import android.view.DisplayCutout import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -29,6 +31,7 @@ import com.android.systemui.SysUICutoutProvider import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE @@ -1051,7 +1054,8 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @Test - fun onMaxBoundsChanged_beforeStart_listenerNotNotified() { + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onMaxBoundsChanged_beforeStart_flagEnabled_listenerNotNotified() { // Start out with an existing configuration with bounds configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) configurationController.onConfigurationChanged(configuration) @@ -1083,7 +1087,41 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @Test - fun onDensityOrFontScaleChanged_beforeStart_listenerNotNotified() { + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onMaxBoundsChanged_beforeStart_flagDisabled_listenerNotified() { + // Start out with an existing configuration with bounds + configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) + configurationController.onConfigurationChanged(configuration) + val provider = + StatusBarContentInsetsProviderImpl( + contextMock, + configurationController, + mock(), + mock(), + mock(), + ) + val listener = + object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + // WHEN the config is updated with new bounds + // but provider is not started + configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789) + configurationController.onConfigurationChanged(configuration) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } + + @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onDensityOrFontScaleChanged_beforeStart_flagEnabled_listenerNotNotified() { configuration.densityDpi = 12 val provider = StatusBarContentInsetsProviderImpl( @@ -1111,6 +1149,36 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { assertThat(listener.triggered).isFalse() } + @Test + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onDensityOrFontScaleChanged_beforeStart_flagDisabled_listenerNotified() { + configuration.densityDpi = 12 + val provider = + StatusBarContentInsetsProviderImpl( + contextMock, + configurationController, + mock(), + mock(), + mock(), + ) + val listener = + object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + // WHEN the config is updated, but the provider is not started + configuration.densityDpi = 20 + configurationController.onConfigurationChanged(configuration) + + // THEN the listener is notified + assertThat(listener.triggered).isTrue() + } + @Test fun onDensityOrFontScaleChanged_afterStart_listenerNotified() { configuration.densityDpi = 12 @@ -1169,7 +1237,8 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @Test - fun onThemeChanged_beforeStart_listenerNotNotified() { + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onThemeChanged_beforeStart_flagEnabled_listenerNotNotified() { val provider = StatusBarContentInsetsProviderImpl( contextMock, @@ -1193,6 +1262,32 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { assertThat(listener.triggered).isFalse() } + @Test + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun onThemeChanged_beforeStart_flagDisabled_listenerNotified() { + val provider = + StatusBarContentInsetsProviderImpl( + contextMock, + configurationController, + mock(), + mock(), + mock(), + ) + val listener = + object : StatusBarContentInsetsChangedListener { + var triggered = false + + override fun onStatusBarContentInsetsChanged() { + triggered = true + } + } + provider.addCallback(listener) + + configurationController.notifyThemeChanged() + + assertThat(listener.triggered).isTrue() + } + private fun assertRects( expected: Rect, actual: Rect, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index f6d439ab26394d236796c3218854ccfb943de075..5a77f3d40e82355afca93fbce0a8fc7f894cf0f8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.os.ParcelUuid -import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET @@ -25,91 +24,78 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.log.table.logcatTableLogBuffer +import com.android.systemui.flags.fake +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot -import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository +import com.android.systemui.statusbar.pipeline.shared.data.repository.fake import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository import com.android.systemui.testKosmos import com.android.systemui.util.CarrierConfigTracker -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class MobileIconsInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() - - private lateinit var underTest: MobileIconsInteractor - private lateinit var connectivityRepository: FakeConnectivityRepository - private lateinit var connectionsRepository: FakeMobileConnectionsRepository - private val userSetupRepository = FakeUserSetupRepository() - private val mobileMappingsProxy = FakeMobileMappingsProxy() - private val flags = - FakeFeatureFlagsClassic().apply { - set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + private val kosmos by lazy { + testKosmos().apply { + mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest" + mobileConnectionsRepository.fake.run { + setMobileConnectionRepositoryMap( + mapOf( + SUB_1_ID to CONNECTION_1, + SUB_2_ID to CONNECTION_2, + SUB_3_ID to CONNECTION_3, + SUB_4_ID to CONNECTION_4, + ) + ) + setActiveMobileDataSubscriptionId(SUB_1_ID) + } + featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) } + } - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) - - private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconsInteractorTest") - - @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - connectivityRepository = FakeConnectivityRepository() - - connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer) - connectionsRepository.setMobileConnectionRepositoryMap( - mapOf( - SUB_1_ID to CONNECTION_1, - SUB_2_ID to CONNECTION_2, - SUB_3_ID to CONNECTION_3, - SUB_4_ID to CONNECTION_4, - ) + // shortcut rename + private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake } + + private val Kosmos.carrierConfigTracker by Fixture { mock() } + + private val Kosmos.underTest by Fixture { + MobileIconsInteractorImpl( + mobileConnectionsRepository, + carrierConfigTracker, + tableLogger = mock(), + connectivityRepository, + FakeUserSetupRepository(), + testScope.backgroundScope, + context, + featureFlagsClassic, ) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) - - underTest = - MobileIconsInteractorImpl( - connectionsRepository, - carrierConfigTracker, - tableLogger = mock(), - connectivityRepository, - userSetupRepository, - testScope.backgroundScope, - context, - flags, - ) } @Test fun filteredSubscriptions_default() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf()) @@ -118,7 +104,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2 @Test fun filteredSubscriptions_moreThanTwo_doesNotFilter() = - testScope.runTest { + kosmos.runTest { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) @@ -129,7 +115,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = - testScope.runTest { + kosmos.runTest { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) val latest by collectLastValue(underTest.filteredSubscriptions) @@ -139,7 +125,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() = - testScope.runTest { + kosmos.runTest { connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) @@ -150,7 +136,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() = - testScope.runTest { + kosmos.runTest { val (sub1, sub2) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_2_ID), @@ -167,7 +153,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() = - testScope.runTest { + kosmos.runTest { val (sub3, sub4) = createSubscriptionPair( subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), @@ -187,7 +173,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() = - testScope.runTest { + kosmos.runTest { val (sub3, sub4) = createSubscriptionPair( subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), @@ -207,7 +193,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() = - testScope.runTest { + kosmos.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), @@ -228,7 +214,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() = - testScope.runTest { + kosmos.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), @@ -249,7 +235,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = - testScope.runTest { + kosmos.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), @@ -258,7 +244,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - connectivityRepository.vcnSubId.value = SUB_3_ID + kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -269,7 +255,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = - testScope.runTest { + kosmos.runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), @@ -278,7 +264,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { ) connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - connectivityRepository.vcnSubId.value = SUB_1_ID + kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -289,9 +275,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() = - testScope.runTest { + kosmos.runTest { // GIVEN the flag is false - flags.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false) + featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false) // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING val sub1 = @@ -313,7 +299,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_filtersOutProvisioningSubs() = - testScope.runTest { + kosmos.runTest { val sub1 = SubscriptionModel( subscriptionId = SUB_1_ID, @@ -326,7 +312,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = SUB_2_ID, isOpportunistic = false, carrierName = "Carrier 2", - profileClass = SubscriptionManager.PROFILE_CLASS_PROVISIONING, + profileClass = PROFILE_CLASS_PROVISIONING, ) connectionsRepository.setSubscriptions(listOf(sub1, sub2)) @@ -339,7 +325,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { /** Note: I'm not sure if this will ever be the case, but we can test it at least */ @Test fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() = - testScope.runTest { + kosmos.runTest { // This is a contrived test case, where the active subId is the one that would // also be filtered by opportunistic filtering. @@ -376,7 +362,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() = - testScope.runTest { + kosmos.runTest { // Grouped filtering only happens when the list of subs is length 2. In this case // we'll show that filtering of provisioning subs happens before, and thus grouped // filtering happens even though the unfiltered list is length 3 @@ -406,7 +392,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = - testScope.runTest { + kosmos.runTest { val notExclusivelyNonTerrestrialSub = SubscriptionModel( isExclusivelyNonTerrestrial = false, @@ -424,7 +410,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = - testScope.runTest { + kosmos.runTest { val exclusivelyNonTerrestrialSub = SubscriptionModel( isExclusivelyNonTerrestrial = true, @@ -442,7 +428,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = - testScope.runTest { + kosmos.runTest { val exclusivelyNonTerrestrialSub = SubscriptionModel( isExclusivelyNonTerrestrial = true, @@ -476,7 +462,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() = - testScope.runTest { + kosmos.runTest { // Exclusively non-terrestrial sub val exclusivelyNonTerrestrialSub = SubscriptionModel( @@ -507,7 +493,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun activeDataConnection_turnedOn() = - testScope.runTest { + kosmos.runTest { CONNECTION_1.setDataEnabled(true) val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) @@ -517,7 +503,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun activeDataConnection_turnedOff() = - testScope.runTest { + kosmos.runTest { CONNECTION_1.setDataEnabled(true) val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) @@ -528,7 +514,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun activeDataConnection_invalidSubId() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID) @@ -539,7 +525,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun failedConnection_default_validated_notFailed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -550,7 +536,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun failedConnection_notDefault_notValidated_notFailed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = false @@ -561,7 +547,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun failedConnection_default_notValidated_failed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -572,7 +558,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun failedConnection_carrierMergedDefault_notValidated_failed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.hasCarrierMergedConnection.value = true @@ -584,7 +570,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { /** Regression test for b/275076959. */ @Test fun failedConnection_dataSwitchInSameGroup_notFailed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -602,7 +588,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun failedConnection_dataSwitchNotInSameGroup_isFailed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -618,7 +604,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun alwaysShowDataRatIcon_configHasTrue() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() @@ -630,7 +616,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun alwaysShowDataRatIcon_configHasFalse() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() @@ -642,7 +628,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun alwaysUseCdmaLevel_configHasTrue() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() @@ -654,7 +640,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun alwaysUseCdmaLevel_configHasFalse() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() @@ -666,7 +652,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun isSingleCarrier_zeroSubscriptions_false() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(emptyList()) @@ -676,7 +662,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun isSingleCarrier_oneSubscription_true() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) @@ -686,7 +672,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun isSingleCarrier_twoSubscriptions_false() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) @@ -696,7 +682,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun isSingleCarrier_updates() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) @@ -708,7 +694,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false @@ -719,7 +705,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true @@ -731,7 +717,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { /** Regression test for b/272586234. */ @Test fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false @@ -742,7 +728,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun mobileIsDefault_updatesWhenRepoUpdates() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true @@ -760,7 +746,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -774,17 +760,17 @@ class MobileIconsInteractorTest : SysuiTestCase() { // After 1s, the force validation bit is still present, so the connection is not marked // as failed - advanceTimeBy(1000) + testScope.advanceTimeBy(1000) assertThat(latest).isFalse() // After 2s, the force validation expires so the connection updates to failed - advanceTimeBy(1001) + testScope.advanceTimeBy(1001) assertThat(latest).isTrue() } @Test fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true @@ -798,7 +784,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) // GIVEN the network starts validated @@ -819,17 +805,17 @@ class MobileIconsInteractorTest : SysuiTestCase() { // THEN the forced validation bit is still used... assertThat(latest).isFalse() - advanceTimeBy(1000) + testScope.advanceTimeBy(1000) assertThat(latest).isFalse() // ... but expires after 2s - advanceTimeBy(1001) + testScope.advanceTimeBy(1001) assertThat(latest).isTrue() } @Test fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true @@ -837,7 +823,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - advanceTimeBy(1000) + testScope.advanceTimeBy(1000) // WHEN another change in same group event happens connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) @@ -847,37 +833,37 @@ class MobileIconsInteractorTest : SysuiTestCase() { // THEN the forced validation remains for exactly 2 more seconds from now // 1.500s from second event - advanceTimeBy(1500) + testScope.advanceTimeBy(1500) assertThat(latest).isFalse() // 2.001s from the second event - advanceTimeBy(501) + testScope.advanceTimeBy(501) assertThat(latest).isTrue() } @Test fun isForceHidden_repoHasMobileHidden_true() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isForceHidden) - connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) + kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) assertThat(latest).isTrue() } @Test fun isForceHidden_repoDoesNotHaveMobileHidden_false() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isForceHidden) - connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) + kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) assertThat(latest).isFalse() } @Test fun iconInteractor_cachedPerSubId() = - testScope.runTest { + kosmos.runTest { val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) @@ -887,7 +873,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = - testScope.runTest { + kosmos.runTest { val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode) connectionsRepository.isDeviceEmergencyCallCapable.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt index 46f822aae7b8d3cd77a59154394dec48fb9fc605..db24d4bc80701441a5050a760bfbf32eb279506e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt @@ -21,18 +21,18 @@ import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS import android.app.StatusBarManager.DISABLE_SYSTEM_INFO -import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.test.runTest -import org.junit.runner.RunWith; +import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt index cdc7aa2dea2afc266ee724fa94dae1fae0d0e12a..9888574071e653e680e94ff4b96b7012542c31a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt @@ -32,6 +32,8 @@ class FakeHomeStatusBarViewBinder : HomeStatusBarViewBinder { override fun bind( view: View, viewModel: HomeStatusBarViewModel, + systemEventChipAnimateIn: ((View) -> Unit)?, + systemEventChipAnimateOut: ((View) -> Unit)?, listener: StatusBarVisibilityChangeListener, ) { this.listener = listener diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt index 02c1540d3d8b14ccb9dfdd03eb4896f802f9329e..eef5753cef8a58b53814a46ce2c3bd14bd03dca0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import android.view.View import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -53,11 +54,14 @@ class FakeHomeStatusBarViewModel : HomeStatusBarViewModel { ) ) - override val isSystemInfoVisible = + override val systemInfoCombinedVis = MutableStateFlow( - HomeStatusBarViewModel.VisibilityModel( - visibility = View.GONE, - shouldAnimateChange = false, + HomeStatusBarViewModel.SystemInfoCombinedVisibilityModel( + HomeStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ), + Idle, ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index b3a73d82122f52120551da62ee2fdf329048b77b..b9cdd06de5a26a25c58316b4163fb487bea7ac45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -58,13 +58,18 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository -import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel +import com.android.systemui.statusbar.events.data.repository.systemStatusEventAnimationRepository +import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn +import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut +import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow @@ -90,6 +95,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository + private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository private lateinit var underTest: HomeStatusBarViewModel @@ -546,25 +552,50 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { @Test fun isSystemInfoVisible_allowedByDisableFlags_visible() = testScope.runTest { - val latest by collectLastValue(underTest.isSystemInfoVisible) + val latest by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() disableFlagsRepository.disableFlags.value = DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) - assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test fun isSystemInfoVisible_notAllowedByDisableFlags_gone() = testScope.runTest { - val latest by collectLastValue(underTest.isSystemInfoVisible) + val latest by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() disableFlagsRepository.disableFlags.value = DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE) - assertThat(latest!!.visibility).isEqualTo(View.GONE) + assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE) + } + + @Test + fun systemInfoCombineVis_animationsPassThrough() = + testScope.runTest { + val latest by collectLastValue(underTest.systemInfoCombinedVis) + transitionKeyguardToGone() + + assertThat(latest!!.baseVisibility) + .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false)) + assertThat(latest!!.animationState).isEqualTo(Idle) + + // WHEN the animation state changes, but the visibility state doesn't change + systemStatusEventAnimationRepository.animationState.value = AnimatingIn + + // THEN the visibility is the same + assertThat(latest!!.baseVisibility) + .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false)) + // THEN the animation state updates + assertThat(latest!!.animationState).isEqualTo(AnimatingIn) + + systemStatusEventAnimationRepository.animationState.value = AnimatingOut + assertThat(latest!!.baseVisibility) + .isEqualTo(VisibilityModel(visibility = View.VISIBLE, shouldAnimateChange = false)) + assertThat(latest!!.animationState).isEqualTo(AnimatingOut) } @Test @@ -573,7 +604,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.GONE, @@ -583,7 +614,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -592,13 +623,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -607,7 +638,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -617,7 +648,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -626,13 +657,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer) assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -641,7 +672,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -651,7 +682,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test @@ -660,14 +691,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null) assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test @@ -676,13 +707,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test @@ -691,14 +722,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() kosmos.shadeTestUtil.setShadeExpansion(0f) assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test @@ -707,13 +738,13 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) kosmos.sceneContainerRepository.snapToScene(Scenes.Gone) assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.VISIBLE) } @Test @@ -722,14 +753,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() kosmos.shadeTestUtil.setShadeExpansion(1f) assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -738,14 +769,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) transitionKeyguardToGone() kosmos.sceneContainerRepository.snapToScene(Scenes.Shade) assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -754,7 +785,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) // Secure camera is an occluding activity keyguardTransitionRepository.sendTransitionSteps( @@ -766,7 +797,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } @Test @@ -775,7 +806,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { testScope.runTest { val clockVisible by collectLastValue(underTest.isClockVisible) val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) - val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis) kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) // Secure camera is an occluding activity @@ -784,7 +815,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) - assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } private fun activeNotificationsStore(notifications: List) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt similarity index 94% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index d823bf57c8240675f947a511251d72f96dc000e0..4eef308a2772ac725c723658a4424daa55143376 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -27,10 +27,14 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory @@ -40,9 +44,7 @@ import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkMode import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.time.fakeSystemClock import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType @@ -53,31 +55,23 @@ import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.kotlin.any -import org.mockito.kotlin.capture +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock -import org.mockito.kotlin.times +import org.mockito.kotlin.never +import org.mockito.kotlin.reset import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -/** - * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests. - * - * Any new tests added here may also need to be added to [WifiRepositoryImplTest]. - */ -@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -class WifiRepositoryImplTest() : SysuiTestCase() { - private val kosmos = testKosmos() +class WifiRepositoryImplTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val userRepository = kosmos.fakeUserRepository // Using lazy means that the class will only be constructed once it's fetched. Because the @@ -108,13 +102,13 @@ class WifiRepositoryImplTest() : SysuiTestCase() { private val callbackCaptor = argumentCaptor() - private val dispatcher = StandardTestDispatcher() - private val testScope = TestScope(dispatcher) + private val dispatcher = kosmos.testDispatcher + private val testScope = kosmos.testScope @Before fun setUp() { userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER)) - whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any())) + whenever(wifiPickerTrackerFactory.create(any(), any(), callbackCaptor.capture(), any())) .thenReturn(wifiPickerTracker) } @@ -122,7 +116,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { fun wifiPickerTrackerCreation_scansDisabled() = testScope.runTest { collectLastValue(underTest.wifiNetwork) - testScope.runCurrent() verify(wifiPickerTracker).disableScanning() } @@ -1001,7 +994,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } @@ -1017,7 +1009,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } @@ -1034,7 +1025,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } @@ -1051,7 +1041,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } @@ -1068,7 +1057,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } @@ -1085,7 +1073,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() } @@ -1103,14 +1090,12 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - testScope.runCurrent() assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() // WHEN the network is lost whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) getCallback().onWifiEntriesChanged() - testScope.runCurrent() // THEN the isWifiConnected updates assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() @@ -1216,9 +1201,6 @@ class WifiRepositoryImplTest() : SysuiTestCase() { assertThat(latest).isEmpty() } - // TODO(b/371586248): This test currently require currentUserContext to be public for testing, - // this needs to - // be updated to capture the argument instead so currentUserContext can be private. @Test @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT) fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() = @@ -1230,16 +1212,16 @@ class WifiRepositoryImplTest() : SysuiTestCase() { ) userRepository.setSelectedUserInfo(PRIMARY_USER) - runCurrent() - val currentUserContext by collectLastValue(underTest.selectedUserContext) - assertThat(currentUserContext).isEqualTo(primaryUserMockContext) - verify(wifiPickerTrackerFactory).create(any(), any(), any(), any()) + collectLastValue(underTest.wifiNetwork) + + val contextCaptor = argumentCaptor() + verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any()) + // If the flag is on, verify that we use the context from #createContextAsUser and we + // do NOT use the top-level context + assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext) } - // TODO(b/371586248): This test currently require currentUserContext to be public for testing, - // this needs to - // be updated to capture the argument instead so currentUserContext can be private. @Test @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT) fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() = @@ -1249,32 +1231,30 @@ class WifiRepositoryImplTest() : SysuiTestCase() { UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext, ) - - runCurrent() userRepository.setSelectedUserInfo(PRIMARY_USER) - runCurrent() - val currentUserContext by collectLastValue(underTest.selectedUserContext) - assertThat(currentUserContext).isEqualTo(primaryUserMockContext) + collectLastValue(underTest.wifiNetwork) + + val contextCaptor = argumentCaptor() + verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any()) + assertThat(contextCaptor.firstValue).isEqualTo(primaryUserMockContext) + reset(wifiPickerTrackerFactory) + + // WHEN we switch to a different user val otherUserMockContext = mock() mContext.prepareCreateContextAsUser( UserHandle.of(ANOTHER_USER_ID), otherUserMockContext, ) - - runCurrent() userRepository.setSelectedUserInfo(ANOTHER_USER) - runCurrent() - val otherUserContext by collectLastValue(underTest.selectedUserContext) - assertThat(otherUserContext).isEqualTo(otherUserMockContext) - verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any()) + // THEN we use the different user's context to create WifiPickerTracker + val newCaptor = argumentCaptor() + verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any()) + assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext) } - // TODO(b/371586248): This test currently require currentUserContext to be public for testing, - // this needs to - // be updated to capture the argument instead so currentUserContext can be private. @Test @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT) fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() = @@ -1285,36 +1265,39 @@ class WifiRepositoryImplTest() : SysuiTestCase() { primaryUserMockContext, ) - runCurrent() userRepository.setSelectedUserInfo(PRIMARY_USER) - runCurrent() - val currentUserContext by collectLastValue(underTest.selectedUserContext) - assertThat(currentUserContext).isEqualTo(primaryUserMockContext) + collectLastValue(underTest.wifiNetwork) + + val contextCaptor = argumentCaptor() + verify(wifiPickerTrackerFactory).create(contextCaptor.capture(), any(), any(), any()) + // If the flag is off, verify that we do NOT use the context from #createContextAsUser + // and we instead use the top-level context + assertThat(contextCaptor.firstValue).isNotEqualTo(primaryUserMockContext) + assertThat(contextCaptor.firstValue).isEqualTo(mContext) + + reset(wifiPickerTrackerFactory) + // WHEN we switch to a different user val otherUserMockContext = mock() mContext.prepareCreateContextAsUser( UserHandle.of(ANOTHER_USER_ID), otherUserMockContext, ) - - runCurrent() userRepository.setSelectedUserInfo(ANOTHER_USER) - runCurrent() - verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any()) + // THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off + verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any()) } private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { - testScope.runCurrent() - return callbackCaptor.value + return callbackCaptor.firstValue } private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback { - testScope.runCurrent() val callbackCaptor = argumentCaptor() verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) - return callbackCaptor.value!! + return callbackCaptor.firstValue } private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry { @@ -1325,10 +1308,9 @@ class WifiRepositoryImplTest() : SysuiTestCase() { } private fun getScanResultsCallback(): WifiManager.ScanResultsCallback { - testScope.runCurrent() val callbackCaptor = argumentCaptor() verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture()) - return callbackCaptor.value!! + return callbackCaptor.firstValue } private companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 9a862fc6a18fbe43f64546dc7bc5ea189e47184f..c5eed7365d920a1d28e9457a959000a329cf0cf0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -24,11 +24,19 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun +import com.android.systemui.statusbar.phone.keyguardBypassController import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -48,6 +56,7 @@ import org.mockito.junit.MockitoRule @RunWith(AndroidJUnit4::class) @EnableFlags(NotificationThrottleHun.FLAG_NAME) class AvalancheControllerTest : SysuiTestCase() { + private val kosmos = testKosmos() // For creating mocks @get:Rule var rule: MockitoRule = MockitoJUnit.rule() @@ -61,7 +70,6 @@ class AvalancheControllerTest : SysuiTestCase() { @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null private val mUiEventLoggerFake = UiEventLoggerFake() @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger - @Mock private lateinit var mBgHandler: Handler private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer())) @@ -76,26 +84,33 @@ class AvalancheControllerTest : SysuiTestCase() { Mockito.`when`( mAccessibilityMgr!!.getRecommendedTimeoutMillis( ArgumentMatchers.anyInt(), - ArgumentMatchers.anyInt() + ArgumentMatchers.anyInt(), ) ) .then { i: InvocationOnMock -> i.getArgument(0) } // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of // declaration, where mocks are null - mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, - mHeadsUpManagerLogger, mBgHandler) + mAvalancheController = + AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler) testableHeadsUpManager = TestableHeadsUpManager( mContext, mLogger, + kosmos.statusBarStateController, + kosmos.keyguardBypassController, + GroupMembershipManagerImpl(), + kosmos.visualStabilityProvider, + kosmos.configurationController, mExecutor, mGlobalSettings, mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, - mAvalancheController + JavaAdapter(kosmos.testScope), + kosmos.shadeInteractor, + mAvalancheController, ) } @@ -270,7 +285,6 @@ class AvalancheControllerTest : SysuiTestCase() { assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry) } - @Test fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() { mAvalancheController.previousHunKey = "" @@ -305,7 +319,7 @@ class AvalancheControllerTest : SysuiTestCase() { mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel") // Next entry is shown - assertThat(mAvalancheController.previousHunKey).isEqualTo(""); + assertThat(mAvalancheController.previousHunKey).isEqualTo("") } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 89aa670e5f0cebc009ef8708f563f416320966aa..abb3e6e0c1f21717056195dd4a555f3437b644c0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -35,6 +35,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import android.app.Notification; import android.app.PendingIntent; import android.app.Person; @@ -49,15 +51,21 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.time.FakeSystemClock; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,7 +81,10 @@ import java.util.List; @SmallTest @TestableLooper.RunWithLooper @RunWith(ParameterizedAndroidJunit4.class) +// TODO(b/378142453): Merge this with BaseHeadsUpManagerTest. public class BaseHeadsUpManagerTest extends SysuiTestCase { + protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -85,6 +96,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); @Mock private Handler mBgHandler; @Mock private DumpManager dumpManager; + @Mock private ShadeInteractor mShadeInteractor; private AvalancheController mAvalancheController; @Mock private AccessibilityManagerWrapper mAccessibilityMgr; @@ -108,8 +120,22 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } private BaseHeadsUpManager createHeadsUpManager() { - return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings, - mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController); + return new TestableHeadsUpManager( + mContext, + mLogger, + mKosmos.getStatusBarStateController(), + mKosmos.getKeyguardBypassController(), + new GroupMembershipManagerImpl(), + mKosmos.getVisualStabilityProvider(), + mKosmos.getConfigurationController(), + mExecutor, + mGlobalSettings, + mSystemClock, + mAccessibilityMgr, + mUiEventLoggerFake, + new JavaAdapter(mKosmos.getTestScope()), + mShadeInteractor, + mAvalancheController); } private NotificationEntry createStickyEntry(int id) { @@ -152,6 +178,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { super.SysuiSetup(); mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger, mBgHandler); + when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(true)); + when(mKosmos.getKeyguardBypassController().getBypassEnabled()).thenReturn(false); } @Test @@ -298,46 +326,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry)); } - @Test - public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() { - final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry notifEntry = - HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext); - - // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned - hum.showNotification(notifEntry); - - final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry( - notifEntry.getKey()); - headsUpEntry.mWasUnpinned = false; - - assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry)); - } - - @Test - public void testShouldHeadsUpBecomePinned_wasUnpinned_false() { - final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry notifEntry = - HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext); - - // Add notifEntry to ANM mAlertEntries map and make it unpinned - hum.showNotification(notifEntry); - - final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry( - notifEntry.getKey()); - headsUpEntry.mWasUnpinned = true; - - assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry)); - } - - @Test - public void testShouldHeadsUpBecomePinned_noFSI_false() { - final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext); - - assertFalse(hum.shouldHeadsUpBecomePinned(entry)); - } - @Test public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt similarity index 100% rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt index 1915e8ede7e7842932b038c7a36486bd26b4b1ec..8ebdbaaa6bf013a86266e6a519943229a93d7622 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar.policy -import android.content.Context import android.os.Handler import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization @@ -26,27 +25,19 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.FakeStatusBarStateController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.phone.ConfigurationControllerImpl -import com.android.systemui.statusbar.notification.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.testKosmos -import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.mockExecutorHandler import com.android.systemui.util.kotlin.JavaAdapter -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.settings.GlobalSettings -import com.android.systemui.util.time.SystemClock import com.google.common.truth.Truth.assertThat import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -78,7 +69,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager @Mock private lateinit var mVSProvider: VisualStabilityProvider - @Mock private lateinit var mStatusBarStateController: StatusBarStateController + val statusBarStateController = FakeStatusBarStateController() @Mock private lateinit var mBypassController: KeyguardBypassController @@ -97,61 +88,16 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager @Mock private lateinit var mBgHandler: Handler - private class TestableHeadsUpManagerPhone( - context: Context, - headsUpManagerLogger: HeadsUpManagerLogger, - groupManager: GroupMembershipManager, - visualStabilityProvider: VisualStabilityProvider, - statusBarStateController: StatusBarStateController, - keyguardBypassController: KeyguardBypassController, - configurationController: ConfigurationController, - globalSettings: GlobalSettings, - systemClock: SystemClock, - executor: DelayableExecutor, - accessibilityManagerWrapper: AccessibilityManagerWrapper, - uiEventLogger: UiEventLogger, - javaAdapter: JavaAdapter, - shadeInteractor: ShadeInteractor, - avalancheController: AvalancheController - ) : - HeadsUpManagerPhone( - context, - headsUpManagerLogger, - statusBarStateController, - keyguardBypassController, - groupManager, - visualStabilityProvider, - configurationController, - mockExecutorHandler(executor), - globalSettings, - systemClock, - executor, - accessibilityManagerWrapper, - uiEventLogger, - javaAdapter, - shadeInteractor, - avalancheController - ) { - init { - mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME - mAutoDismissTime = TEST_AUTO_DISMISS_TIME - } - - /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */ - fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean { - return shouldHeadsUpBecomePinned(entry) - } - } - - private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone { - return TestableHeadsUpManagerPhone( + private fun createHeadsUpManagerPhone(): BaseHeadsUpManager { + return BaseHeadsUpManager( mContext, mHeadsUpManagerLogger, + statusBarStateController, + mBypassController, mGroupManager, mVSProvider, - mStatusBarStateController, - mBypassController, mConfigurationController, + mockExecutorHandler(mExecutor), mGlobalSettings, mSystemClock, mExecutor, @@ -159,20 +105,22 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager mUiEventLogger, mJavaAdapter, mShadeInteractor, - mAvalancheController + mAvalancheController, ) } @Before fun setUp() { whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false)) + whenever(mShadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false)) + whenever(mBypassController.bypassEnabled).thenReturn(false) whenever(mVSProvider.isReorderingAllowed).thenReturn(true) val accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java) whenever( accessibilityMgr.getRecommendedTimeoutMillis( ArgumentMatchers.anyInt(), - ArgumentMatchers.anyInt() + ArgumentMatchers.anyInt(), ) ) .thenReturn(TEST_AUTO_DISMISS_TIME) @@ -205,7 +153,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager hmp.removeNotification( entry.key, /* releaseImmediately= */ false, - /* reason= */ "swipe out" + /* reason= */ "swipe out", ) Assert.assertTrue(removedImmediately) Assert.assertFalse(hmp.isHeadsUpEntry(entry.key)) @@ -245,6 +193,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong()) Assert.assertTrue(hmp.isHeadsUpEntry(entry.key)) } + @Test @EnableFlags(NotificationThrottleHun.FLAG_NAME) fun testShowNotification_removeWhenReorderingAllowedTrue() { @@ -253,7 +202,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) hmp.showNotification(notifEntry) - assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue(); + assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue() } @Test @@ -264,7 +213,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) hmp.showNotification(notifEntry) - assertThat(notifEntry.isSeenInShade).isTrue(); + assertThat(notifEntry.isSeenInShade).isTrue() } @Test @@ -275,197 +224,136 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) hmp.showNotification(notifEntry) - assertThat(notifEntry.isSeenInShade).isFalse(); + assertThat(notifEntry.isSeenInShade).isFalse() } + @Test + fun testShouldHeadsUpBecomePinned_noFSI_false() = + testScope.runTest { + val hum = createHeadsUpManagerPhone() + statusBarStateController.setState(StatusBarState.KEYGUARD) + + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + Assert.assertFalse(hum.shouldHeadsUpBecomePinned(entry)) + } + + @Test + fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() = + testScope.runTest { + val hum = createHeadsUpManagerPhone() + statusBarStateController.setState(StatusBarState.KEYGUARD) + + val notifEntry = + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) + + // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned + hum.showNotification(notifEntry) + + val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.mWasUnpinned = false + + Assert.assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry)) + } + + @Test + fun testShouldHeadsUpBecomePinned_wasUnpinned_false() = + testScope.runTest { + val hum = createHeadsUpManagerPhone() + statusBarStateController.setState(StatusBarState.KEYGUARD) + + val notifEntry = + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) + + // Add notifEntry to ANM mAlertEntries map and make it unpinned + hum.showNotification(notifEntry) + + val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.mWasUnpinned = true + + Assert.assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry)) + } + @Test fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false)) - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(StatusBarState.SHADE) runCurrent() // THEN - Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry)) } @Test fun shouldHeadsUpBecomePinned_shadeLocked_false() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(StatusBarState.SHADE_LOCKED) runCurrent() // THEN - Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry)) } @Test fun shouldHeadsUpBecomePinned_shadeUnknown_false() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(1207) runCurrent() // THEN - Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry)) } @Test fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() whenever(mBypassController.bypassEnabled).thenReturn(true) - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(StatusBarState.KEYGUARD) runCurrent() // THEN - Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry)) } @Test fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() whenever(mBypassController.bypassEnabled).thenReturn(false) - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(StatusBarState.KEYGUARD) runCurrent() // THEN - Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry)) } @Test fun shouldHeadsUpBecomePinned_shadeExpanded_false() = testScope.runTest { // GIVEN - val statusBarStateController = FakeStatusBarStateController() whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true)) - val hmp = - TestableHeadsUpManagerPhone( - mContext, - mHeadsUpManagerLogger, - mGroupManager, - mVSProvider, - statusBarStateController, - mBypassController, - mConfigurationController, - mGlobalSettings, - mSystemClock, - mExecutor, - mAccessibilityManagerWrapper, - mUiEventLogger, - mJavaAdapter, - mShadeInteractor, - mAvalancheController - ) + val hmp = createHeadsUpManagerPhone() val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) statusBarStateController.setState(StatusBarState.SHADE) runCurrent() // THEN - Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry)) + Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry)) } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java index 3f33d2f89f5ef3b9a05bbb28225e2650b9a2e98d..8593f6a08b5aeed04fe818159cb5122140e9fb69 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java @@ -21,9 +21,9 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.view.RotationPolicy; @@ -37,7 +37,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class RotationLockControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java index 3efabd743141aefaa7126211c54d40888ccf99f8..59987f413ad6641d470553c18ed59c7bd8c6dd4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; import static org.mockito.Mockito.spy; @@ -28,8 +27,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; +import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; @@ -37,16 +42,39 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { private HeadsUpEntry mLastCreatedEntry; - TestableHeadsUpManager(Context context, + TestableHeadsUpManager( + Context context, HeadsUpManagerLogger logger, + StatusBarStateController statusBarStateController, + KeyguardBypassController bypassController, + GroupMembershipManager groupMembershipManager, + VisualStabilityProvider visualStabilityProvider, + ConfigurationController configurationController, DelayableExecutor executor, GlobalSettings globalSettings, SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor, AvalancheController avalancheController) { - super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock, - executor, accessibilityManagerWrapper, uiEventLogger, avalancheController); + super( + context, + logger, + statusBarStateController, + bypassController, + groupMembershipManager, + visualStabilityProvider, + configurationController, + mockExecutorHandler(executor), + globalSettings, + systemClock, + executor, + accessibilityManagerWrapper, + uiEventLogger, + javaAdapter, + shadeInteractor, + avalancheController); mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME; mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME; @@ -61,11 +89,6 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { return mLastCreatedEntry; } - @Override - public int getContentFlag() { - return FLAG_CONTENT_VIEW_CONTRACTED; - } - // The following are only implemented by HeadsUpManagerPhone. If you need them, use that. @Override public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt index 39836e2ce32fcc304142b158db1cdb59f0b4049f..e686edec8514f77fce423d621bb942ba19858140 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt @@ -18,9 +18,13 @@ package com.android.systemui.statusbar.policy.ui.dialog.viewmodel +import android.app.AutomaticZenRule import android.content.Intent import android.content.applicationContext import android.provider.Settings +import android.service.notification.SystemZenRules +import android.service.notification.ZenModeConfig +import android.service.notification.ZenModeConfig.ScheduleInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder @@ -35,6 +39,7 @@ import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import java.util.Calendar import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.test.runCurrent @@ -60,6 +65,9 @@ class ModesDialogViewModelTest : SysuiTestCase() { private lateinit var underTest: ModesDialogViewModel + private lateinit var timeScheduleMode: ZenMode + private lateinit var timeScheduleInfo: ScheduleInfo + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -71,6 +79,21 @@ class ModesDialogViewModelTest : SysuiTestCase() { kosmos.mockModesDialogDelegate, kosmos.mockModesDialogEventLogger, ) + + timeScheduleInfo = ZenModeConfig.ScheduleInfo() + timeScheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY) + timeScheduleInfo.startHour = 11 + timeScheduleInfo.endHour = 15 + timeScheduleMode = + TestModeBuilder() + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME) + .setManualInvocationAllowed(true) + .setConditionId(ZenModeConfig.toScheduleConditionId(timeScheduleInfo)) + .setTriggerDescription( + SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, timeScheduleInfo) + ) + .build() } @Test @@ -325,17 +348,19 @@ class ModesDialogViewModelTest : SysuiTestCase() { .setTriggerDescription(null) .setEnabled(false, /* byUser= */ false) .build(), + timeScheduleMode, ) ) runCurrent() - assertThat(tiles!!).hasSize(6) + assertThat(tiles!!).hasSize(7) assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough") assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome") assertThat(tiles!![2].subtext).isEqualTo("Not set") assertThat(tiles!![3].subtext).isEqualTo("Off") assertThat(tiles!![4].subtext).isEqualTo("On") assertThat(tiles!![5].subtext).isEqualTo("Not set") + assertThat(tiles!![6].subtext).isEqualTo(timeScheduleMode.triggerDescription) } @Test @@ -381,11 +406,12 @@ class ModesDialogViewModelTest : SysuiTestCase() { .setTriggerDescription(null) .setEnabled(false, /* byUser= */ false) .build(), + timeScheduleMode, ) ) runCurrent() - assertThat(tiles!!).hasSize(6) + assertThat(tiles!!).hasSize(7) with(tiles?.elementAt(0)!!) { assertThat(this.stateDescription).isEqualTo("Off") assertThat(this.subtextDescription).isEqualTo("When the going gets tough") @@ -410,6 +436,12 @@ class ModesDialogViewModelTest : SysuiTestCase() { assertThat(this.stateDescription).isEqualTo("Off") assertThat(this.subtextDescription).isEqualTo("Not set") } + with(tiles?.elementAt(6)!!) { + assertThat(this.stateDescription).isEqualTo("Off") + assertThat(this.subtextDescription) + .isEqualTo(SystemZenRules.getDaysOfWeekFull(context, timeScheduleInfo) + + ", " + SystemZenRules.getTimeSummary(context, timeScheduleInfo)) + } // All tiles have the same long click info tiles!!.forEach { assertThat(it.onLongClickLabel).isEqualTo("Open settings") } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7a9d0179b239b78a2c4421e63bf8a8d67c8f18bf --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.window + +import android.platform.test.annotations.EnableFlags +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.testKosmos +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val fakeDisplayRepository = kosmos.displayRepository + private val testScope = kosmos.testScope + + private val underTest by lazy { kosmos.multiDisplayStatusBarWindowControllerStore } + + @Before + fun start() { + underTest.start() + } + + @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) } + + @Test + fun beforeDisplayRemoved_doesNotStopInstances() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + verify(instance, never()).stop() + } + + @Test + fun displayRemoved_stopsInstance() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) + + verify(instance).stop() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt index 6e66287c1683d8e0239b1f5d023aa0b8d172a34c..61c719319de80c6772fec07d2cff5dc6e628a54e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImplTest.kt @@ -17,12 +17,17 @@ package com.android.systemui.statusbar.window import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.view.fakeWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.fragments.fragmentService import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.policy.statusBarConfigurationController import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any @@ -37,6 +42,9 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() { testKosmos().also { it.statusBarWindowViewInflater = it.fakeStatusBarWindowViewInflater } private val underTest = kosmos.statusBarWindowControllerImpl + private val fakeExecutor = kosmos.fakeExecutor + private val fakeWindowManager = kosmos.fakeWindowManager + private val mockFragmentService = kosmos.fragmentService private val fakeStatusBarWindowViewInflater = kosmos.fakeStatusBarWindowViewInflater private val statusBarConfigurationController = kosmos.statusBarConfigurationController @@ -59,4 +67,64 @@ class StatusBarWindowControllerImplTest : SysuiTestCase() { verify(mockWindowView, never()).setStatusBarConfigurationController(any()) } + + @Test + @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME) + fun stop_statusBarModernizationFlagEnabled_doesNotRemoveFragment() { + val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first() + + underTest.stop() + fakeExecutor.runAllReady() + + verify(mockFragmentService, never()).removeAndDestroy(windowView) + } + + @Test + @DisableFlags(StatusBarRootModernization.FLAG_NAME) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun stop_statusBarModernizationFlagDisabled_removesFragment() { + val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first() + + underTest.stop() + fakeExecutor.runAllReady() + + verify(mockFragmentService).removeAndDestroy(windowView) + } + + @Test + @DisableFlags(StatusBarRootModernization.FLAG_NAME) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun stop_statusBarModernizationFlagDisabled_removesFragmentOnExecutor() { + val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first() + + underTest.stop() + + verify(mockFragmentService, never()).removeAndDestroy(windowView) + fakeExecutor.runAllReady() + verify(mockFragmentService).removeAndDestroy(windowView) + } + + @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun stop_removesWindowViewFromWindowManager() { + underTest.attach() + underTest.stop() + + assertThat(fakeWindowManager.addedViews).isEmpty() + } + + @Test(expected = IllegalStateException::class) + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun stop_connectedDisplaysFlagDisabled_crashes() { + underTest.stop() + } + + @Test + fun attach_windowViewAddedToWindowManager() { + val windowView = fakeStatusBarWindowViewInflater.inflatedMockViews.first() + + underTest.attach() + + assertThat(fakeWindowManager.addedViews.keys).containsExactly(windowView) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt similarity index 98% rename from packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index 9592b280be344410573f620e90ce7dd8111be2ab..798380a920026a135b969654c9f548dcee379aa2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.stylus import android.hardware.BatteryState import android.hardware.input.InputManager -import android.testing.AndroidTestingRunner import android.view.InputDevice +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags @@ -35,7 +35,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class StylusUsiPowerStartableTest : SysuiTestCase() { @Mock lateinit var inputManager: InputManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt similarity index 99% rename from packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt index 01dd60ae22007e599fc54043a2a5e67c6d32e609..4351d28ecf4544c43a89c6bac43f4b617feb69bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt @@ -21,8 +21,8 @@ import android.content.DialogInterface import android.content.DialogInterface.BUTTON_NEGATIVE import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.DialogInterface.BUTTON_POSITIVE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -36,7 +36,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class TestableAlertDialogTest : SysuiTestCase() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt index 2e6d0fc847bb0c7742067ef9a1f25822ac245052..e25bf13c41f2b6fdbe3a6db84c472d4467f8d778 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -27,7 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -57,7 +57,7 @@ class SettingsProxyTest : SysuiTestCase() { @Before fun setUp() { testScope = TestScope(testDispatcher) - mSettings = FakeSettingsProxy(testDispatcher) + mSettings = FakeSettingsProxy(testScope) mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} } @@ -92,7 +92,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -104,7 +104,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserver( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -116,7 +116,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverAsync( TEST_SETTING, notifyForDescendants = true, - mContentObserver + mContentObserver, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -154,7 +154,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -166,7 +166,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserver( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) @@ -178,7 +178,7 @@ class SettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverAsync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -202,7 +202,7 @@ class SettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, false, mContentObserver, - it + it, ) } } @@ -382,15 +382,15 @@ class SettingsProxyTest : SysuiTestCase() { assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) } - private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy { + private class FakeSettingsProxy(val testScope: CoroutineScope) : SettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) private val settingToValueMap: MutableMap = mutableMapOf() override fun getContentResolver() = mContentResolver - override val backgroundDispatcher: CoroutineDispatcher - get() = testDispatcher + override val settingsScope: CoroutineScope + get() = testScope override fun getUriFor(name: String) = Uri.parse(StringBuilder().append("content://settings/").append(name).toString()) @@ -408,7 +408,7 @@ class SettingsProxyTest : SysuiTestCase() { name: String, value: String?, tag: String?, - makeDefault: Boolean + makeDefault: Boolean, ): Boolean { settingToValueMap[name] = value return true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt index 00b8cd04bdafa45f713f1b38ce50f9b7eb9b32bb..5787f7dfc61fb9811cdd3c7125e8db281038555f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -28,7 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher @@ -36,7 +36,6 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -51,14 +50,9 @@ class UserSettingsProxyTest : SysuiTestCase() { private var userId = MAIN_USER_ID private val testDispatcher = StandardTestDispatcher() - private var mSettings: UserSettingsProxy = FakeUserSettingsProxy({ userId }, testDispatcher) + private val testScope = TestScope(testDispatcher) + private var mSettings: UserSettingsProxy = FakeUserSettingsProxy({ userId }, testScope) private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} - private lateinit var testScope: TestScope - - @Before - fun setUp() { - testScope = TestScope(testDispatcher) - } @Test fun registerContentObserverForUser_inputString_success() = @@ -69,7 +63,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -82,7 +76,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -96,7 +90,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -107,14 +101,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -125,16 +119,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), - eq( - true, - ), + eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -145,7 +137,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING, notifyForDescendants = true, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -153,7 +145,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -166,7 +158,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -179,7 +171,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -189,7 +181,7 @@ class UserSettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverForUserAsync( TEST_SETTING_URI, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() @@ -198,7 +190,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -213,7 +205,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, mContentObserver, userId, - runnable + runnable, ) testScope.advanceUntilIdle() assertThat(callbackCalled).isTrue() @@ -226,14 +218,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -244,14 +236,14 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -262,7 +254,7 @@ class UserSettingsProxyTest : SysuiTestCase() { TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, - userId + userId, ) testScope.advanceUntilIdle() verify(mSettings.getContentResolver()) @@ -270,7 +262,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(MAIN_USER_ID) + eq(MAIN_USER_ID), ) } @@ -283,7 +275,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -296,7 +288,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -309,7 +301,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } } @@ -320,14 +312,14 @@ class UserSettingsProxyTest : SysuiTestCase() { mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, - mContentObserver + mContentObserver, ) verify(mSettings.getContentResolver()) .registerContentObserver( eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -340,7 +332,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } @@ -354,7 +346,7 @@ class UserSettingsProxyTest : SysuiTestCase() { eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), - eq(0) + eq(0), ) } } @@ -557,7 +549,7 @@ class UserSettingsProxyTest : SysuiTestCase() { */ private class FakeUserSettingsProxy( override val currentUserProvider: SettingsProxy.CurrentUserIdProvider, - val testDispatcher: CoroutineDispatcher + val testScope: CoroutineScope, ) : UserSettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) @@ -569,8 +561,8 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun getUriFor(name: String) = Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString()) - override val backgroundDispatcher: CoroutineDispatcher - get() = testDispatcher + override val settingsScope: CoroutineScope + get() = testScope override fun getStringForUser(name: String, userHandle: Int) = userIdToSettingsValueMap[userHandle]?.get(name) ?: "" @@ -578,7 +570,7 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun putString( name: String, value: String?, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean { userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value) return true @@ -588,7 +580,7 @@ class UserSettingsProxyTest : SysuiTestCase() { name: String, value: String?, tag: String?, - makeDefault: Boolean + makeDefault: Boolean, ): Boolean { putStringForUser(name, value, DEFAULT_USER_ID) return true @@ -605,7 +597,7 @@ class UserSettingsProxyTest : SysuiTestCase() { tag: String?, makeDefault: Boolean, userHandle: Int, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean { userIdToSettingsValueMap[userHandle]?.set(name, value) return true diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java similarity index 96% rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java index 1ff9548486ab5fdb3e25339248090f44abd6f468..c6bfb351b57d3d5db6458ebc23f8bf578205f0c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java @@ -21,9 +21,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.animation.Animator; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.mockito.MockitoAnnotations; @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeepAwakeAnimationListenerTest extends SysuiTestCase { @Mock WakeLock mWakeLock; KeepAwakeAnimationListener mKeepAwakeAnimationListener; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 1914867863290ff8e80890120c73518e48014660..75f3386ed695d414179de3f934043ef8e8801ad2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,14 +40,13 @@ import android.media.IAudioService; import android.media.session.MediaSession; import android.os.Handler; import android.os.Process; -import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.view.accessibility.AccessibilityManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.settingslib.flags.Flags; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -64,6 +64,10 @@ import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.volume.domain.interactor.AudioSharingInteractor; +import com.android.systemui.volume.domain.interactor.FakeAudioSharingInteractor; +import com.android.systemui.volume.shared.VolumeLogger; + +import kotlinx.coroutines.test.TestScope; import org.junit.Before; import org.junit.Test; @@ -94,6 +98,9 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { private RingerModeLiveData mRingerModeInternalLiveData; private final FakeThreadFactory mThreadFactory = new FakeThreadFactory( new FakeExecutor(new FakeSystemClock())); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope); + private FakeAudioSharingInteractor mFakeAudioSharingInteractor; @Mock private AudioManager mAudioManager; @Mock @@ -117,9 +124,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock - private AudioSharingInteractor mAudioSharingInteractor; - @Mock - private JavaAdapter mJavaAdapter; + private VolumeLogger mVolumeLogger; @Before @@ -136,6 +141,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mCallback = mock(VolumeDialogControllerImpl.C.class); mThreadFactory.setLooper(TestableLooper.get(this).getLooper()); + mFakeAudioSharingInteractor = spy(new FakeAudioSharingInteractor()); + mFakeAudioSharingInteractor.setAudioSharingVolumeBarAvailable(true); mVolumeController = new TestableVolumeDialogControllerImpl( mContext, @@ -155,8 +162,9 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mUserTracker, mDumpManager, mCallback, - mAudioSharingInteractor, - mJavaAdapter); + mFakeAudioSharingInteractor, + mJavaAdapter, + mVolumeLogger); mVolumeController.setEnableDialogs(true, true); } @@ -305,13 +313,13 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX) public void testSetStreamVolume_setSecondaryDeviceVolume() { mVolumeController.setStreamVolume( VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST, /* level= */ 100); Objects.requireNonNull(TestableLooper.get(this)).processAllMessages(); + mTestScope.getTestScheduler().advanceUntilIdle(); - verify(mAudioSharingInteractor).setStreamVolume(100); + verify(mFakeAudioSharingInteractor).setStreamVolume(100); } static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl { @@ -336,7 +344,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { DumpManager dumpManager, C callback, AudioSharingInteractor audioSharingInteractor, - JavaAdapter javaAdapter) { + JavaAdapter javaAdapter, + VolumeLogger volumeLogger) { super( context, broadcastDispatcher, @@ -355,7 +364,8 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { userTracker, dumpManager, audioSharingInteractor, - javaAdapter); + javaAdapter, + volumeLogger); mCallbacks = callback; ArgumentCaptor observerCaptor = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt index 76b7b8f946b4af2fe6ec39acdb324d5e1277629a..a9c352df9a26e234a6556f3531bc8bf52222b505 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt @@ -49,9 +49,10 @@ import com.android.systemui.testKosmos import com.android.systemui.util.RingerModeLiveData import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeThreadFactory +import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.time.fakeSystemClock import com.android.systemui.volume.data.repository.audioRepository -import com.android.systemui.volume.domain.interactor.audioSharingInteractor +import com.android.systemui.volume.domain.interactor.FakeAudioSharingInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -76,6 +77,9 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { private val kosmos: Kosmos = testKosmos() private val audioManager: AudioManager = mock {} private val callbacks: VolumeDialogController.Callbacks = mock {} + private val javaAdapter: JavaAdapter = JavaAdapter(kosmos.testScope) + private val fakeAudioSharingInteractor: FakeAudioSharingInteractor = + FakeAudioSharingInteractor() private lateinit var threadFactory: FakeThreadFactory private lateinit var underTest: VolumeDialogControllerImpl @@ -87,6 +91,8 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { threadFactory = FakeThreadFactory(FakeExecutor(fakeSystemClock)).apply { setLooper(looper) } broadcastDispatcherContext = testableContext + fakeAudioSharingInteractor.setAudioSharingVolumeBarAvailable(true) + underTest = VolumeDialogControllerImpl( applicationContext, @@ -108,7 +114,8 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { activityManager, mock { on { userContext }.thenReturn(applicationContext) }, dumpManager, - audioSharingInteractor, + fakeAudioSharingInteractor, + javaAdapter, mock {}, ) .apply { @@ -129,6 +136,7 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { }, ) testableLooper.processAllMessages() + testScheduler.advanceUntilIdle() verify(callbacks) { 1 * { onStateChanged(any()) } } } @@ -163,6 +171,7 @@ class VolumeDialogControllerImplTestKt : SysuiTestCase() { emitVolumeChange(AudioManager.STREAM_SYSTEM, AudioManager.FLAG_SHOW_UI) runCurrent() TestableLooper.get(this@VolumeDialogControllerImplTestKt).processAllMessages() + testScheduler.advanceUntilIdle() verify(callbacks) { 1 * { onShowRequested(any(), any(), any()) } } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 7d55169e048aec372fea3ea95c24399fba129c2e..fb9e96c820cf993ce07f1570bec511e8e3637347 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -13,11 +13,20 @@ */ package com.android.systemui.plugins.clocks +import android.content.Context import android.graphics.Rect import android.graphics.drawable.Drawable +import android.util.DisplayMetrics import android.view.View import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.internal.annotations.Keep +import com.android.internal.policy.SystemBarUtils import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.Plugin import com.android.systemui.plugins.annotations.GeneratedImport @@ -130,7 +139,9 @@ data class ClockMessageBuffers( /** Message buffer for large clock rendering */ val largeClockMessageBuffer: MessageBuffer, -) +) { + constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {} +} data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float) @@ -149,7 +160,7 @@ interface ClockFaceLayout { @ProtectedReturn("return constraints;") /** Custom constraints to apply to preview ConstraintLayout. */ - fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet + fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) } @@ -169,13 +180,84 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { return constraints } - override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet { - return constraints + override fun applyPreviewConstraints( + context: Context, + constraints: ConstraintSet, + ): ConstraintSet { + return applyDefaultPreviewConstraints(context, constraints) } override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) { // Default clock doesn't need detailed control of view } + + companion object { + fun applyDefaultPreviewConstraints( + context: Context, + constraints: ConstraintSet, + ): ConstraintSet { + constraints.apply { + val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large") + constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT) + constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT) + constrainMaxHeight(lockscreenClockViewLargeId, 0) + + val largeClockTopMargin = + SystemBarUtils.getStatusBarHeight(context) + + getDimen(context, "small_clock_padding_top") + + getDimen(context, "keyguard_smartspace_top_offset") + + getDimen(context, "date_weather_view_height") + + getDimen(context, "enhanced_smartspace_height") + connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin) + connect(lockscreenClockViewLargeId, START, PARENT_ID, START) + connect(lockscreenClockViewLargeId, END, PARENT_ID, END) + + // In preview, we'll show UDFPS icon for UDFPS devices + // and nothing for non-UDFPS devices, + // and we're not planning to add this vide in clockHostView + // so we only need position of device entry icon to constrain clock + // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection + val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom") + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx + + connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin) + val smallClockViewId = getId(context, "lockscreen_clock_view") + constrainWidth(smallClockViewId, WRAP_CONTENT) + constrainHeight(smallClockViewId, getDimen(context, "small_clock_height")) + connect( + smallClockViewId, + START, + PARENT_ID, + START, + getDimen(context, "clock_padding_start") + + getDimen(context, "status_view_margin_horizontal"), + ) + val smallClockTopMargin = + getDimen(context, "keyguard_clock_top_margin") + + SystemBarUtils.getStatusBarHeight(context) + connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin) + } + return constraints + } + + fun getId(context: Context, name: String): Int { + val packageName = context.packageName + val res = context.packageManager.getResourcesForApplication(packageName) + val id = res.getIdentifier(name, "id", packageName) + return id + } + + fun getDimen(context: Context, name: String): Int { + val packageName = context.packageName + val res = context.packageManager.getResourcesForApplication(packageName) + val id = res.getIdentifier(name, "dimen", packageName) + return if (id == 0) 0 else res.getDimensionPixelSize(id) + } + } } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/pods/Android.bp b/packages/SystemUI/pods/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..e45f3170d9add8413bf6ed5024e73b0a3a2cb465 --- /dev/null +++ b/packages/SystemUI/pods/Android.bp @@ -0,0 +1,22 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], + // The package default_visibility specified here is inherited to subpackages that do not + // specify default_visibility: + default_visibility: ["//visibility:private"], +} diff --git a/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..df90be8ecb972a3c734ffb1cc7dea6a5cf5c38d1 --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp @@ -0,0 +1,34 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +soong_namespace { +} + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "api", + srcs: [ + "**/*.java", + "**/*.kt", + ], + libs: [ + "jsr330", + ], + visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"], +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java b/packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java rename to packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java diff --git a/packages/SystemUI/pods/com/android/systemui/retail/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..f04784885c108bf2e0827489cbdd4a46ccfa3874 --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +soong_namespace { +} + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "impl", + srcs: ["*.kt"], + libs: [ + "jsr330", + "dagger2", + "SystemUICommon", + "kotlinx_coroutines", + ], + static_libs: [ + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:impl", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain:impl", + ], + visibility: ["//frameworks/base/packages/SystemUI"], +} diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt similarity index 84% rename from packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt rename to packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt index e8639498b7c012cc02ccca55d1a540c24d50bfae..c20e368f1690cd9f6e325c2657e2d75c654d4cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt +++ b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.systemui.retail.dagger +package com.android.systemui.retail import com.android.systemui.retail.data.repository.RetailModeRepository -import com.android.systemui.retail.data.repository.RetailModeSettingsRepository +import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository import com.android.systemui.retail.domain.interactor.RetailModeInteractor -import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl +import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl import dagger.Binds import dagger.Module diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..f148a7c69ecc8103342ebc47d718a7e3668d411e --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp @@ -0,0 +1,55 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +soong_namespace { +} + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "api", + srcs: ["repository/*.kt"], + libs: [ + "kotlinx_coroutines", + ], + visibility: [ + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain", + ], +} + +java_library { + name: "impl", + srcs: ["repository/impl/*.kt"], + libs: [ + "jsr330", + "kotlinx_coroutines", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api", + "SystemUICommon", + ], + static_libs: [ + "api", + ], + visibility: [ + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain", + ], +} diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..c9eac2588811db529600466b4c188db959b92410 --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.retail.data.repository + +import kotlinx.coroutines.flow.StateFlow + +/** Repository to track if the device is in Retail mode */ +interface RetailModeRepository { + /** Flow of whether the device is currently in retail mode. */ + val retailMode: StateFlow + + /** Last value of whether the device is in retail mode. */ + val inRetailMode: Boolean + get() = retailMode.value +} diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt similarity index 87% rename from packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt rename to packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt index 09fd7df15dc2aefe5f2297979421a0832bd9e242..8955263595f3619e5e608a559adf88b952d62017 100644 --- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt +++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.retail.data.repository +package com.android.systemui.retail.data.repository.impl import android.database.ContentObserver import android.provider.Settings @@ -22,6 +22,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.retail.data.repository.RetailModeRepository import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -34,16 +35,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -/** Repository to track if the device is in Retail mode */ -interface RetailModeRepository { - /** Flow of whether the device is currently in retail mode. */ - val retailMode: StateFlow - - /** Last value of whether the device is in retail mode. */ - val inRetailMode: Boolean - get() = retailMode.value -} - /** * Tracks [Settings.Global.DEVICE_DEMO_MODE]. * diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..787861ce5eb84af298b327c12ec50483ea57d344 --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +soong_namespace { +} + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "api", + srcs: ["interactor/*.kt"], + visibility: [ + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail", + ], +} + +java_library { + name: "impl", + srcs: ["interactor/impl/*.kt"], + libs: [ + "jsr330", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:api", + ], + static_libs: [ + "api", + ], + visibility: [ + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail", + ], +} diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt new file mode 100644 index 0000000000000000000000000000000000000000..748e34d430d8226c7ad510371989fff3bcbd7aff --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.retail.domain.interactor + +/** Interactor to determine if the device is currently in retail mode */ +interface RetailModeInteractor { + /** Whether the device is currently in retail mode */ + val isInRetailMode: Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt similarity index 79% rename from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt rename to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt index eea452c78ea570de0ccdba59ab03215bf8e0e19c..8dbe562c2ed7cb672fb3366e260e651b0c63f251 100644 --- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt +++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt @@ -14,18 +14,13 @@ * limitations under the License. */ -package com.android.systemui.retail.domain.interactor +package com.android.systemui.retail.domain.interactor.impl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.retail.data.repository.RetailModeRepository +import com.android.systemui.retail.domain.interactor.RetailModeInteractor import javax.inject.Inject -/** Interactor to determine if the device is currently in retail mode */ -interface RetailModeInteractor { - /** Whether the device is currently in retail mode */ - val isInRetailMode: Boolean -} - @SysUISingleton class RetailModeInteractorImpl @Inject diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..1aa7729614081539559131f4459a4f16bd87a41c --- /dev/null +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +soong_namespace { +} + +package { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +java_library { + name: "api", + srcs: [ + "*.java", + "*.kt", + ], + libs: [ + "//frameworks/libs/systemui:tracinglib-platform", + "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api", + "SystemUICommon", + "androidx.annotation_annotation", + "kotlinx_coroutines_android", + "jsr330", + ], + kotlincflags: ["-Xjvm-default=all"], + visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"], +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java similarity index 84% rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java index 7fcabe4a4363747df92f5bf1a29dcb2ba854f3fe..6b909529e33ae3d9ceb620fe3a3316b596f30f16 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java @@ -23,9 +23,9 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; -import com.android.systemui.util.kotlin.SettingsSingleThreadBackground; +import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; @@ -33,13 +33,13 @@ import javax.inject.Inject; @SuppressLint("StaticSettingsProvider") class GlobalSettingsImpl implements GlobalSettings { private final ContentResolver mContentResolver; - private final CoroutineDispatcher mBgDispatcher; + private final CoroutineScope mSettingsScope; @Inject GlobalSettingsImpl(ContentResolver contentResolver, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; - mBgDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -56,8 +56,8 @@ class GlobalSettingsImpl implements GlobalSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java similarity index 88% rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java index c29648186d5429488d7e16433434de4a4ac2114f..ae89a5f021c24d1188bf3bbce9ca8a01a31670d8 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -21,25 +21,25 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; -import com.android.systemui.util.kotlin.SettingsSingleThreadBackground; +import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; class SecureSettingsImpl implements SecureSettings { private final ContentResolver mContentResolver; private final CurrentUserIdProvider mCurrentUserProvider; - private final CoroutineDispatcher mBgDispatcher; + private final CoroutineScope mSettingsScope; @Inject SecureSettingsImpl( ContentResolver contentResolver, CurrentUserIdProvider currentUserProvider, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; mCurrentUserProvider = currentUserProvider; - mBgDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -62,8 +62,8 @@ class SecureSettingsImpl implements SecureSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt similarity index 93% rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt index 5d0b0d55e1f69e4fda51eb503dcbb52a7f5cc45c..154d3cc9eda1eb7b8e9a1435a0c7b1f7b4ffb9ab 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt @@ -23,10 +23,12 @@ import android.provider.Settings.SettingNotFoundException import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import com.android.app.tracing.TraceUtils.trace -import com.android.systemui.coroutines.newTracingContext +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.app.tracing.coroutines.nameCoroutine +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -47,11 +49,14 @@ interface SettingsProxy { /** Returns the [ContentResolver] this instance was constructed with. */ fun getContentResolver(): ContentResolver - /** - * Returns the background [CoroutineDispatcher] that the async APIs will use for a specific - * implementation. - */ - val backgroundDispatcher: CoroutineDispatcher + /** Returns the [CoroutineScope] that the async APIs will use. */ + val settingsScope: CoroutineScope + + @OptIn(ExperimentalStdlibApi::class) + fun settingsDispatcherContext(name: String): CoroutineContext { + return (settingsScope.coroutineContext[CoroutineDispatcher] ?: EmptyCoroutineContext) + + nameCoroutine(name) + } /** * Construct the content URI for a particular name/value pair, useful for monitoring changes @@ -82,7 +87,7 @@ interface SettingsProxy { * wish to synchronize execution. */ suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-A")) { registerContentObserverSync(getUriFor(name), settingsObserver) } } @@ -94,7 +99,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-A")).launch { + settingsScope.launch("registerContentObserverAsync-A") { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -111,7 +116,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-B")).launch { + settingsScope.launch("registerContentObserverAsync-B") { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -134,7 +139,9 @@ interface SettingsProxy { * wish to synchronize execution. */ suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { registerContentObserverSync(uri, settingsObserver) } + withContext(settingsDispatcherContext("registerContentObserver-B")) { + registerContentObserverSync(uri, settingsObserver) + } } /** @@ -144,7 +151,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-C")).launch { + settingsScope.launch("registerContentObserverAsync-C") { registerContentObserverSync(uri, settingsObserver) } @@ -161,7 +168,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-D")).launch { + settingsScope.launch("registerContentObserverAsync-D") { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -188,9 +195,9 @@ interface SettingsProxy { suspend fun registerContentObserver( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-C")) { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } } @@ -206,7 +213,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-E")).launch { + settingsScope.launch("registerContentObserverAsync-E") { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -224,7 +231,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-F")).launch { + settingsScope.launch("registerContentObserverAsync-F") { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -239,7 +246,7 @@ interface SettingsProxy { fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { trace({ "SP#registerObserver#[$uri]" }) { getContentResolver() @@ -257,9 +264,9 @@ interface SettingsProxy { suspend fun registerContentObserver( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-D")) { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } } @@ -275,7 +282,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-G")).launch { + settingsScope.launch("registerContentObserverAsync-G") { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -293,7 +300,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-H")).launch { + settingsScope.launch("registerContentObserverAsync-H") { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -319,7 +326,9 @@ interface SettingsProxy { * async block if they wish to synchronize execution. */ suspend fun unregisterContentObserver(settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { unregisterContentObserverSync(settingsObserver) } + withContext(settingsDispatcherContext("unregisterContentObserver")) { + unregisterContentObserverSync(settingsObserver) + } } /** @@ -330,7 +339,7 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-I")).launch { + settingsScope.launch("unregisterContentObserverAsync") { unregisterContentObserver(settingsObserver) } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt similarity index 100% rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java similarity index 96% rename from packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java index e13981dfe5c7440041fdb7915f408e0e5c2a8d5d..5632a36a294200dcda0cb93a68e94df3f3205622 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.util.kotlin; +package com.android.systemui.util.settings; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java similarity index 100% rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java similarity index 87% rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java index e670b2c2edd0f036feebaf8d3a7b346bfbc48692..65d1c276cbfab71ac6239f884db1208774ecccb2 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java @@ -21,24 +21,24 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; -import com.android.systemui.util.kotlin.SettingsSingleThreadBackground; +import com.android.systemui.util.settings.SettingsSingleThreadBackground; -import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; import javax.inject.Inject; class SystemSettingsImpl implements SystemSettings { private final ContentResolver mContentResolver; private final CurrentUserIdProvider mCurrentUserProvider; - private final CoroutineDispatcher mBgCoroutineDispatcher; + private final CoroutineScope mSettingsScope; @Inject SystemSettingsImpl(ContentResolver contentResolver, CurrentUserIdProvider currentUserProvider, - @SettingsSingleThreadBackground CoroutineDispatcher bgDispatcher) { + @SettingsSingleThreadBackground CoroutineScope settingsScope) { mContentResolver = contentResolver; mCurrentUserProvider = currentUserProvider; - mBgCoroutineDispatcher = bgDispatcher; + mSettingsScope = settingsScope; } @NonNull @@ -61,8 +61,8 @@ class SystemSettingsImpl implements SystemSettings { @NonNull @Override - public CoroutineDispatcher getBackgroundDispatcher() { - return mBgCoroutineDispatcher; + public CoroutineScope getSettingsScope() { + return mSettingsScope; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt similarity index 91% rename from packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt rename to packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt index 4b03df6b00705438b57fcbba2d4f87bf19f5443b..1a5517059ca4cd2eed47535f164bcb6fc24787ee 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -23,13 +23,11 @@ import android.net.Uri import android.os.UserHandle import android.provider.Settings.SettingNotFoundException import com.android.app.tracing.TraceUtils.trace -import com.android.systemui.coroutines.newTracingContext +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault -import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -73,13 +71,13 @@ interface UserSettingsProxy : SettingsProxy { } override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-A")) { registerContentObserverForUserSync(uri, settingsObserver, userId) } } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-A")).launch { + settingsScope.launch("registerContentObserverAsync-A") { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -96,9 +94,9 @@ interface UserSettingsProxy : SettingsProxy { override suspend fun registerContentObserver( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserver-B")) { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } } @@ -113,7 +111,7 @@ interface UserSettingsProxy : SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-B")).launch { + settingsScope.launch("registerContentObserverAsync-B") { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -126,7 +124,7 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserSync( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -141,9 +139,9 @@ interface UserSettingsProxy : SettingsProxy { suspend fun registerContentObserverForUser( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-A")) { registerContentObserverForUserSync(name, settingsObserver, userHandle) } } @@ -158,7 +156,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-C")).launch { + settingsScope.launch("registerContentObserverForUserAsync-A") { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -167,7 +165,7 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserSync( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync(uri, false, settingsObserver, userHandle) } @@ -182,9 +180,9 @@ interface UserSettingsProxy : SettingsProxy { suspend fun registerContentObserverForUser( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-B")) { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } } @@ -199,7 +197,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-D")).launch { + settingsScope.launch("registerContentObserverForUserAsync-B") { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -216,7 +214,7 @@ interface UserSettingsProxy : SettingsProxy { userHandle: Int, @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-E")).launch { + settingsScope.launch("registerContentObserverForUserAsync-C") { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -231,13 +229,13 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } @@ -252,14 +250,14 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-C")) { registerContentObserverForUserSync( name, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } } @@ -275,7 +273,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) { - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-F")).launch { + settingsScope.launch("registerContentObserverForUserAsync-D") { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, @@ -291,7 +289,7 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { trace({ "USP#registerObserver#[$uri]" }) { getContentResolver() @@ -299,7 +297,7 @@ interface UserSettingsProxy : SettingsProxy { uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle) + getRealUserHandle(userHandle), ) Unit } @@ -316,14 +314,14 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - withContext(backgroundDispatcher) { + withContext(settingsDispatcherContext("registerContentObserverForUser-D")) { registerContentObserverForUserSync( uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle) + getRealUserHandle(userHandle), ) } } @@ -339,7 +337,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-G")).launch { + settingsScope.launch("registerContentObserverForUserAsync-E") { registerContentObserverForUserSync( uri, notifyForDescendants, @@ -385,7 +383,7 @@ interface UserSettingsProxy : SettingsProxy { tag: String?, makeDefault: Boolean, @UserIdInt userHandle: Int, - overrideableByRestore: Boolean + overrideableByRestore: Boolean, ): Boolean override fun getInt(name: String, default: Int): Int { diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml similarity index 94% rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml rename to packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml index c83b6d38a0e13e5486168d4b95967ddbba9db5fc..dfefb9d166af7902a6d9ed4bcfa43c33d69ae826 100644 --- a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml +++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml @@ -14,7 +14,8 @@ limitations under the License. --> - @@ -30,8 +31,8 @@ android:end="20dp" android:gravity="end|center_vertical"> diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml similarity index 100% rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml rename to packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml similarity index 66% rename from packages/SystemUI/res/drawable/volume_dialog_spacer.xml rename to packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml index 3c60784cf6b67910155c370bff377bb85119e797..c708d2280161c1aeef4fd9784cef78275d38ed34 100644 --- a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml +++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml @@ -1,4 +1,5 @@ - - - - - - + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_check.xml b/packages/SystemUI/res/drawable/ic_check.xml new file mode 100644 index 0000000000000000000000000000000000000000..80707d876146b6bcc2ccaddff0d79f6f777f027a --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_check.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_background_top.xml b/packages/SystemUI/res/drawable/volume_background_top.xml index 75849beeb01616b51641672e79e73e21e8fa4eb5..132572a41a36890993d170a5d2ba9617d915a38c 100644 --- a/packages/SystemUI/res/drawable/volume_background_top.xml +++ b/packages/SystemUI/res/drawable/volume_background_top.xml @@ -17,7 +17,6 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - diff --git a/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml b/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml new file mode 100644 index 0000000000000000000000000000000000000000..a8d4d2ece07f56d716dd79c21fd1bc401bec7739 --- /dev/null +++ b/packages/SystemUI/res/layout/activity_rear_display_front_screen_on.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index b9314c7ae0e02547b372c2032542cc56f3dcf329..124aec6a92ddefe1434e25ccfff8e96b79d7b62c 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -27,8 +27,8 @@ + android:gravity="center_vertical" + android:textSize="14sp" /> @@ -105,7 +106,7 @@ android:paddingStart="36dp" android:text="@string/turn_on_bluetooth" android:clickable="false" - android:textAppearance="@style/TextAppearance.Dialog.Title" + android:textAppearance="@style/TextAppearance.BluetoothTileDialog" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" app:layout_constraintStart_toStartOf="parent" @@ -146,7 +147,7 @@ android:paddingEnd="15dp" android:paddingStart="36dp" android:clickable="false" - android:textAppearance="@style/TextAppearance.Dialog.Title" + android:textAppearance="@style/TextAppearance.BluetoothTileDialog" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@+id/bluetooth_auto_on_toggle" app:layout_constraintStart_toStartOf="parent" @@ -187,7 +188,8 @@ android:layout_marginTop="20dp" android:paddingStart="36dp" android:paddingEnd="40dp" - android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:textSize="14sp" + android:textAppearance="@style/TextAppearance.BluetoothTileDialog" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/bluetooth_auto_on_toggle_info_icon" /> @@ -204,7 +206,7 @@ android:id="@+id/see_all_button" style="@style/BluetoothTileDialog.Device" android:paddingEnd="0dp" - android:paddingStart="20dp" + android:paddingStart="26dp" android:background="@drawable/bluetooth_tile_dialog_bg_off" android:layout_width="0dp" android:layout_height="64dp" @@ -214,11 +216,11 @@ app:layout_constraintTop_toBottomOf="@+id/device_list" app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button" android:drawableStart="@drawable/ic_arrow_forward" - android:drawablePadding="20dp" + android:drawablePadding="26dp" android:drawableTint="?android:attr/textColorPrimary" android:text="@string/see_all_bluetooth_devices" android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" + android:textAppearance="@style/TextAppearance.BluetoothTileDialog" android:textDirection="locale" android:textAlignment="viewStart" android:maxLines="1" @@ -229,7 +231,7 @@ android:id="@+id/pair_new_device_button" style="@style/BluetoothTileDialog.Device" android:paddingEnd="0dp" - android:paddingStart="20dp" + android:paddingStart="26dp" android:background="@drawable/bluetooth_tile_dialog_bg_off" android:layout_width="0dp" android:layout_height="64dp" @@ -238,11 +240,11 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/see_all_button" android:drawableStart="@drawable/ic_add" - android:drawablePadding="20dp" + android:drawablePadding="26dp" android:drawableTint="?android:attr/textColorPrimary" android:text="@string/pair_new_bluetooth_devices" android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" + android:textAppearance="@style/TextAppearance.BluetoothTileDialog" android:textDirection="locale" android:textAlignment="viewStart" android:maxLines="1" @@ -259,6 +261,7 @@