diff options
532 files changed, 15053 insertions, 4434 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index b5f398b28b9e..ce3e985e22d5 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -36,6 +36,7 @@ aconfig_srcjars = [ ":com.android.hardware.input-aconfig-java{.generated_srcjars}", ":com.android.input.flags-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", + ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", ":telecom_flags_core_java_lib{.generated_srcjars}", ":telephony_flags_core_java_lib{.generated_srcjars}", ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", @@ -664,6 +665,19 @@ cc_aconfig_library { aconfig_declarations: "device_policy_aconfig_flags", } +// JobScheduler +aconfig_declarations { + name: "framework-jobscheduler-job.flags-aconfig", + package: "android.app.job", + srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"], +} + +java_aconfig_library { + name: "framework-jobscheduler-job.flags-aconfig-java", + aconfig_declarations: "framework-jobscheduler-job.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Notifications aconfig_declarations { name: "android.service.notification.flags-aconfig", diff --git a/Android.bp b/Android.bp index a402c57689d6..78ffd6fb5e69 100644 --- a/Android.bp +++ b/Android.bp @@ -414,13 +414,25 @@ java_defaults { ], } +// Collection of non updatable unbundled jars. The list here should match +// |non_updatable_modules| variable in frameworks/base/api/api.go. +java_library { + name: "framework-non-updatable-unbundled-impl-libs", + static_libs: [ + "framework-location.impl", + "framework-nfc.impl", + ], + sdk_version: "core_platform", + installable: false, +} + // Separated so framework-minus-apex-defaults can be used without the libs dependency java_defaults { name: "framework-minus-apex-with-libs-defaults", defaults: ["framework-minus-apex-defaults"], libs: [ "framework-virtualization.stubs.module_lib", - "framework-location.impl", + "framework-non-updatable-unbundled-impl-libs", ], } @@ -451,7 +463,7 @@ java_library { stem: "framework", apex_available: ["//apex_available:platform"], visibility: [ - "//frameworks/base/location", + "//frameworks/base:__subpackages__", ], compile_dex: false, headers_only: true, @@ -514,8 +526,8 @@ java_library { installable: false, // this lib is a build-only library static_libs: [ "app-compat-annotations", - "framework-location.impl", "framework-minus-apex", + "framework-non-updatable-unbundled-impl-libs", "framework-updatable-stubs-module_libs_api", ], sdk_version: "core_platform", diff --git a/THERMAL_OWNERS b/THERMAL_OWNERS new file mode 100644 index 000000000000..b95b7e84191c --- /dev/null +++ b/THERMAL_OWNERS @@ -0,0 +1,3 @@ +lpy@google.com +wvw@google.com +xwxw@google.com diff --git a/apct-tests/perftests/OWNERS b/apct-tests/perftests/OWNERS index 4c57e640c141..8ff3f9bc6620 100644 --- a/apct-tests/perftests/OWNERS +++ b/apct-tests/perftests/OWNERS @@ -1,12 +1,11 @@ -balejs@google.com carmenjackson@google.com -cfijalkovich@google.com dualli@google.com edgararriaga@google.com -jpakaravoor@google.com +jdduke@google.com jreck@google.com #{LAST_RESORT_SUGGESTION} kevinjeon@google.com philipcuadra@google.com +shayba@google.com shombert@google.com timmurray@google.com wessam@google.com diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp index 9366ff2d81a9..e1b3241e051e 100644 --- a/apct-tests/perftests/core/Android.bp +++ b/apct-tests/perftests/core/Android.bp @@ -66,6 +66,7 @@ android_test { errorprone: { javacflags: [ "-Xep:ReturnValueIgnored:WARN", + "-Xep:UnnecessaryStringBuilder:OFF", ], }, } diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig new file mode 100644 index 000000000000..f5e33a80211b --- /dev/null +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -0,0 +1,8 @@ +package: "android.app.job" + +flag { + name: "job_debug_info_apis" + namespace: "backstage_power" + description: "Add APIs to let apps attach debug information to jobs" + bug: "293491637" +} diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 9961c4fdf3f7..742ed5f2eeb7 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -26,6 +26,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -47,13 +48,17 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.Trace; +import android.util.ArraySet; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Objects; +import java.util.Set; /** * Container of data passed to the {@link android.app.job.JobScheduler} fully encapsulating the @@ -423,6 +428,15 @@ public class JobInfo implements Parcelable { */ public static final int CONSTRAINT_FLAG_STORAGE_NOT_LOW = 1 << 3; + /** @hide */ + public static final int MAX_NUM_DEBUG_TAGS = 32; + + /** @hide */ + public static final int MAX_DEBUG_TAG_LENGTH = 127; + + /** @hide */ + public static final int MAX_TRACE_TAG_LENGTH = Trace.MAX_SECTION_NAME_LEN; + @UnsupportedAppUsage private final int jobId; private final PersistableBundle extras; @@ -454,6 +468,9 @@ public class JobInfo implements Parcelable { private final int mPriority; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int flags; + private final ArraySet<String> mDebugTags; + @Nullable + private final String mTraceTag; /** * Unique job id associated with this application (uid). This is the same job ID @@ -724,6 +741,33 @@ public class JobInfo implements Parcelable { } /** + * @see JobInfo.Builder#addDebugTag(String) + */ + @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @NonNull + public Set<String> getDebugTags() { + return Collections.unmodifiableSet(mDebugTags); + } + + /** + * @see JobInfo.Builder#addDebugTag(String) + * @hide + */ + @NonNull + public ArraySet<String> getDebugTagsArraySet() { + return mDebugTags; + } + + /** + * @see JobInfo.Builder#setTraceTag(String) + */ + @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @Nullable + public String getTraceTag() { + return mTraceTag; + } + + /** * @see JobInfo.Builder#setExpedited(boolean) */ public boolean isExpedited() { @@ -860,6 +904,12 @@ public class JobInfo implements Parcelable { if (flags != j.flags) { return false; } + if (!mDebugTags.equals(j.mDebugTags)) { + return false; + } + if (!Objects.equals(mTraceTag, j.mTraceTag)) { + return false; + } return true; } @@ -904,6 +954,12 @@ public class JobInfo implements Parcelable { hashCode = 31 * hashCode + mBias; hashCode = 31 * hashCode + mPriority; hashCode = 31 * hashCode + flags; + if (mDebugTags.size() > 0) { + hashCode = 31 * hashCode + mDebugTags.hashCode(); + } + if (mTraceTag != null) { + hashCode = 31 * hashCode + mTraceTag.hashCode(); + } return hashCode; } @@ -946,6 +1002,17 @@ public class JobInfo implements Parcelable { mBias = in.readInt(); mPriority = in.readInt(); flags = in.readInt(); + final int numDebugTags = in.readInt(); + mDebugTags = new ArraySet<>(); + for (int i = 0; i < numDebugTags; ++i) { + final String tag = in.readString(); + if (tag == null) { + throw new IllegalStateException("malformed parcel"); + } + mDebugTags.add(tag.intern()); + } + final String traceTag = in.readString(); + mTraceTag = traceTag == null ? null : traceTag.intern(); } private JobInfo(JobInfo.Builder b) { @@ -978,6 +1045,8 @@ public class JobInfo implements Parcelable { mBias = b.mBias; mPriority = b.mPriority; flags = b.mFlags; + mDebugTags = b.mDebugTags; + mTraceTag = b.mTraceTag; } @Override @@ -1024,6 +1093,14 @@ public class JobInfo implements Parcelable { out.writeInt(mBias); out.writeInt(mPriority); out.writeInt(this.flags); + // Explicitly write out values here to avoid double looping to intern the strings + // when unparcelling. + final int numDebugTags = mDebugTags.size(); + out.writeInt(numDebugTags); + for (int i = 0; i < numDebugTags; ++i) { + out.writeString(mDebugTags.valueAt(i)); + } + out.writeString(mTraceTag); } public static final @android.annotation.NonNull Creator<JobInfo> CREATOR = new Creator<JobInfo>() { @@ -1168,6 +1245,8 @@ public class JobInfo implements Parcelable { private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY; /** Easy way to track whether the client has tried to set a back-off policy. */ private boolean mBackoffPolicySet = false; + private final ArraySet<String> mDebugTags = new ArraySet<>(); + private String mTraceTag; /** * Initialize a new Builder to construct a {@link JobInfo}. @@ -1222,6 +1301,51 @@ public class JobInfo implements Parcelable { mPriority = job.getPriority(); } + /** + * Add a debug tag to help track what this job is for. The tags may show in debug dumps + * or app metrics. Do not put personally identifiable information (PII) in the tag. + * <p> + * Tags have the following requirements: + * <ul> + * <li>Tags cannot be more than 127 characters.</li> + * <li> + * Since leading and trailing whitespace can lead to hard-to-debug issues, + * tags should not include leading or trailing whitespace. + * All tags will be {@link String#trim() trimmed}. + * </li> + * <li>An empty String (after trimming) is not allowed.</li> + * <li>Should not have personally identifiable information (PII).</li> + * <li>A job cannot have more than 32 tags.</li> + * </ul> + * + * @param tag A debug tag that helps describe what the job is for. + * @return This object for method chaining + */ + @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @NonNull + public Builder addDebugTag(@NonNull String tag) { + mDebugTags.add(validateDebugTag(tag)); + return this; + } + + /** @hide */ + @NonNull + public void addDebugTags(@NonNull Set<String> tags) { + mDebugTags.addAll(tags); + } + + /** + * Remove a tag set via {@link #addDebugTag(String)}. + * @param tag The tag to remove + * @return This object for method chaining + */ + @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @NonNull + public Builder removeDebugTag(@NonNull String tag) { + mDebugTags.remove(tag); + return this; + } + /** @hide */ @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) @@ -1997,6 +2121,24 @@ public class JobInfo implements Parcelable { } /** + * Set a tag that will be used in {@link android.os.Trace traces}. + * Since this is a trace tag, it must follow the rules set in + * {@link android.os.Trace#beginSection(String)}, such as it cannot be more + * than 127 Unicode code units. + * Additionally, since leading and trailing whitespace can lead to hard-to-debug issues, + * they will be {@link String#trim() trimmed}. + * An empty String (after trimming) is not allowed. + * @param traceTag The tag to use in traces. + * @return This object for method chaining + */ + @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @NonNull + public Builder setTraceTag(@Nullable String traceTag) { + mTraceTag = validateTraceTag(traceTag); + return this; + } + + /** * @return The job object to hand to the JobScheduler. This object is immutable. */ public JobInfo build() { @@ -2209,6 +2351,62 @@ public class JobInfo implements Parcelable { "A user-initiated data transfer job must specify a valid network type"); } } + + if (mDebugTags.size() > MAX_NUM_DEBUG_TAGS) { + throw new IllegalArgumentException( + "Can't have more than " + MAX_NUM_DEBUG_TAGS + " tags"); + } + final ArraySet<String> validatedDebugTags = new ArraySet<>(); + for (int i = 0; i < mDebugTags.size(); ++i) { + validatedDebugTags.add(validateDebugTag(mDebugTags.valueAt(i))); + } + mDebugTags.clear(); + mDebugTags.addAll(validatedDebugTags); + + validateTraceTag(mTraceTag); + } + + /** + * Returns a sanitized debug tag if valid, or throws an exception if not. + * @hide + */ + @NonNull + public static String validateDebugTag(@Nullable String debugTag) { + if (debugTag == null) { + throw new NullPointerException("debug tag cannot be null"); + } + debugTag = debugTag.trim(); + if (debugTag.isEmpty()) { + throw new IllegalArgumentException("debug tag cannot be empty"); + } + if (debugTag.length() > MAX_DEBUG_TAG_LENGTH) { + throw new IllegalArgumentException( + "debug tag cannot be more than " + MAX_DEBUG_TAG_LENGTH + " characters"); + } + return debugTag.intern(); + } + + /** + * Returns a sanitized trace tag if valid, or throws an exception if not. + * @hide + */ + @Nullable + public static String validateTraceTag(@Nullable String traceTag) { + if (traceTag == null) { + return null; + } + traceTag = traceTag.trim(); + if (traceTag.isEmpty()) { + throw new IllegalArgumentException("trace tag cannot be empty"); + } + if (traceTag.length() > MAX_TRACE_TAG_LENGTH) { + throw new IllegalArgumentException( + "traceTag tag cannot be more than " + MAX_TRACE_TAG_LENGTH + " characters"); + } + if (traceTag.contains("|") || traceTag.contains("\n") || traceTag.contains("\0")) { + throw new IllegalArgumentException("Trace tag cannot contain |, \\n, or \\0"); + } + return traceTag.intern(); } /** diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java index a720bafee1d3..6f8014faf91a 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java @@ -22,7 +22,7 @@ import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import static com.android.server.alarm.AlarmManagerService.PRIORITY_NORMAL; -import static com.android.server.alarm.AlarmManagerService.clampPositive; +import static com.android.server.alarm.AlarmManagerService.addClampPositive; import android.app.AlarmManager; import android.app.IAlarmListener; @@ -148,7 +148,7 @@ class Alarm { mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed; mWhenElapsed = requestedWhenElapsed; this.windowLength = windowLength; - mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength); + mMaxWhenElapsed = addClampPositive(requestedWhenElapsed, windowLength); repeatInterval = interval; operation = op; listener = rec; @@ -244,8 +244,8 @@ class Alarm { final long oldMaxWhenElapsed = mMaxWhenElapsed; // windowLength should always be >= 0 here. - final long maxRequestedElapsed = clampPositive( - mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength); + final long maxRequestedElapsed = addClampPositive( + mPolicyWhenElapsed[REQUESTER_POLICY_INDEX], windowLength); mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed); return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed); 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 384a480af4e9..1bd8da8256c3 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -1417,15 +1417,15 @@ public class AlarmManagerService extends SystemService { if (futurity < MIN_FUZZABLE_INTERVAL) { futurity = 0; } - long maxElapsed = triggerAtTime + (long) (0.75 * futurity); + long maxElapsed = addClampPositive(triggerAtTime, (long) (0.75 * futurity)); // For non-repeating alarms, window is capped at a maximum of one hour from the requested // delivery time. This allows for inexact-while-idle alarms to be slightly more reliable. // In practice, the delivery window should generally be much smaller than that // when the device is not idling. if (interval == 0) { - maxElapsed = Math.min(maxElapsed, triggerAtTime + INTERVAL_HOUR); + maxElapsed = Math.min(maxElapsed, addClampPositive(triggerAtTime, INTERVAL_HOUR)); } - return clampPositive(maxElapsed); + return maxElapsed; } // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries. @@ -1512,6 +1512,18 @@ public class AlarmManagerService extends SystemService { return (val >= 0) ? val : Long.MAX_VALUE; } + static long addClampPositive(long val1, long val2) { + long val = val1 + val2; + if (val >= 0) { + return val; + } else if (val1 >= 0 && val2 >= 0) { + /* Both are +ve, so overflow happened. */ + return Long.MAX_VALUE; + } else { + return 0; + } + } + /** * Sends alarms that were blocked due to user applied background restrictions - either because * the user lifted those or the uid came to foreground. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 721a8bdce57a..6449edcd3103 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -557,6 +557,11 @@ public final class JobServiceContext implements ServiceConnection { Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", traceTag, getId()); } + if (job.getAppTraceTag() != null) { + // Use the job's ID to distinguish traces since the ID will be unique per app. + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler", + job.getAppTraceTag(), job.getJobId()); + } try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); } catch (RemoteException e) { @@ -1616,6 +1621,10 @@ public final class JobServiceContext implements ServiceConnection { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", getId()); } + if (completedJob.getAppTraceTag() != null) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler", + completedJob.getJobId()); + } try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), loggingInternalStopReason); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index d466f0d8912c..afcbddad611e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -510,6 +510,8 @@ public final class JobStore { private static final String XML_TAG_ONEOFF = "one-off"; private static final String XML_TAG_EXTRAS = "extras"; private static final String XML_TAG_JOB_WORK_ITEM = "job-work-item"; + private static final String XML_TAG_DEBUG_INFO = "debug-info"; + private static final String XML_TAG_DEBUG_TAG = "debug-tag"; private void migrateJobFilesAsync() { synchronized (mLock) { @@ -805,6 +807,7 @@ public final class JobStore { writeExecutionCriteriaToXml(out, jobStatus); writeBundleToXml(jobStatus.getJob().getExtras(), out); writeJobWorkItemsToXml(out, jobStatus); + writeDebugInfoToXml(out, jobStatus); out.endTag(null, XML_TAG_JOB); numJobs++; @@ -991,6 +994,26 @@ public final class JobStore { } } + private void writeDebugInfoToXml(@NonNull TypedXmlSerializer out, + @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException { + final ArraySet<String> debugTags = jobStatus.getJob().getDebugTagsArraySet(); + final int numTags = debugTags.size(); + final String traceTag = jobStatus.getJob().getTraceTag(); + if (traceTag == null && numTags == 0) { + return; + } + out.startTag(null, XML_TAG_DEBUG_INFO); + if (traceTag != null) { + out.attribute(null, "trace-tag", traceTag); + } + for (int i = 0; i < numTags; ++i) { + out.startTag(null, XML_TAG_DEBUG_TAG); + out.attribute(null, "tag", debugTags.valueAt(i)); + out.endTag(null, XML_TAG_DEBUG_TAG); + } + out.endTag(null, XML_TAG_DEBUG_INFO); + } + private void writeJobWorkItemsToXml(@NonNull TypedXmlSerializer out, @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException { // Write executing first since they're technically at the front of the queue. @@ -1449,6 +1472,18 @@ public final class JobStore { jobWorkItems = readJobWorkItemsFromXml(parser); } + if (eventType == XmlPullParser.START_TAG + && XML_TAG_DEBUG_INFO.equals(parser.getName())) { + try { + jobBuilder.setTraceTag(parser.getAttributeValue(null, "trace-tag")); + } catch (Exception e) { + Slog.wtf(TAG, "Invalid trace tag persisted to disk", e); + } + parser.next(); + jobBuilder.addDebugTags(readDebugTagsFromXml(parser)); + eventType = parser.nextTag(); // Consume </debug-info> + } + final JobInfo builtJob; try { // Don't perform prefetch-deadline check here. Apps targeting S- shouldn't have @@ -1721,6 +1756,33 @@ public final class JobStore { return null; } } + + @NonNull + private Set<String> readDebugTagsFromXml(TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + Set<String> debugTags = new ArraySet<>(); + + for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT; + eventType = parser.next()) { + final String tagName = parser.getName(); + if (!XML_TAG_DEBUG_TAG.equals(tagName)) { + // We're no longer operating with debug tags. + break; + } + if (debugTags.size() < JobInfo.MAX_NUM_DEBUG_TAGS) { + final String debugTag; + try { + debugTag = JobInfo.validateDebugTag(parser.getAttributeValue(null, "tag")); + } catch (Exception e) { + Slog.wtf(TAG, "Invalid debug tag persisted to disk", e); + continue; + } + debugTags.add(debugTag); + } + } + + return debugTags; + } } /** Set of all tracked jobs. */ diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 0cf0cc5dcd22..e06006f25d3f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -640,22 +640,27 @@ public final class ConnectivityController extends RestrictingController implemen if (mCcConfig.mShouldReprocessNetworkCapabilities || (mFlexibilityController.isEnabled() != mCcConfig.mFlexIsEnabled)) { AppSchedulingModuleThread.getHandler().post(() -> { - boolean shouldUpdateJobs = false; - for (int i = 0; i < mAvailableNetworks.size(); ++i) { - CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); - if (metadata == null || metadata.networkCapabilities == null) { - continue; + boolean flexAffinitiesChanged = false; + boolean flexAffinitiesSatisfied = false; + synchronized (mLock) { + for (int i = 0; i < mAvailableNetworks.size(); ++i) { + CachedNetworkMetadata metadata = mAvailableNetworks.valueAt(i); + if (metadata == null) { + continue; + } + if (updateTransportAffinitySatisfaction(metadata)) { + // Something changed. Update jobs. + flexAffinitiesChanged = true; + } + flexAffinitiesSatisfied |= metadata.satisfiesTransportAffinities; } - boolean satisfies = satisfiesTransportAffinities(metadata.networkCapabilities); - if (metadata.satisfiesTransportAffinities != satisfies) { - metadata.satisfiesTransportAffinities = satisfies; - // Something changed. Update jobs. - shouldUpdateJobs = true; + if (flexAffinitiesChanged) { + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_CONNECTIVITY, + flexAffinitiesSatisfied, sElapsedRealtimeClock.millis()); + updateAllTrackedJobsLocked(false); } } - if (shouldUpdateJobs) { - updateAllTrackedJobsLocked(false); - } }); } } @@ -1059,6 +1064,22 @@ public final class ConnectivityController extends RestrictingController implemen return false; } + /** + * Updates {@link CachedNetworkMetadata#satisfiesTransportAffinities} in the given + * {@link CachedNetworkMetadata} object. + * @return true if the satisfaction changed + */ + private boolean updateTransportAffinitySatisfaction( + @NonNull CachedNetworkMetadata cachedNetworkMetadata) { + final boolean satisfiesAffinities = + satisfiesTransportAffinities(cachedNetworkMetadata.networkCapabilities); + if (cachedNetworkMetadata.satisfiesTransportAffinities != satisfiesAffinities) { + cachedNetworkMetadata.satisfiesTransportAffinities = satisfiesAffinities; + return true; + } + return false; + } + private boolean satisfiesTransportAffinities(@Nullable NetworkCapabilities capabilities) { if (!mFlexibilityController.isEnabled()) { return true; @@ -1552,7 +1573,9 @@ public final class ConnectivityController extends RestrictingController implemen } } cnm.networkCapabilities = capabilities; - cnm.satisfiesTransportAffinities = satisfiesTransportAffinities(capabilities); + if (updateTransportAffinitySatisfaction(cnm)) { + maybeUpdateFlexConstraintLocked(cnm); + } maybeRegisterSignalStrengthCallbackLocked(capabilities); updateTrackedJobsLocked(-1, network); postAdjustCallbacks(); @@ -1566,8 +1589,13 @@ public final class ConnectivityController extends RestrictingController implemen } synchronized (mLock) { final CachedNetworkMetadata cnm = mAvailableNetworks.remove(network); - if (cnm != null && cnm.networkCapabilities != null) { - maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities); + if (cnm != null) { + if (cnm.networkCapabilities != null) { + maybeUnregisterSignalStrengthCallbackLocked(cnm.networkCapabilities); + } + if (cnm.satisfiesTransportAffinities) { + maybeUpdateFlexConstraintLocked(null); + } } for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) { UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u); @@ -1639,6 +1667,37 @@ public final class ConnectivityController extends RestrictingController implemen } } } + + /** + * Maybe call {@link FlexibilityController#setConstraintSatisfied(int, boolean, long)} + * if the network affinity state has changed. + */ + @GuardedBy("mLock") + private void maybeUpdateFlexConstraintLocked( + @Nullable CachedNetworkMetadata cachedNetworkMetadata) { + if (cachedNetworkMetadata != null + && cachedNetworkMetadata.satisfiesTransportAffinities) { + mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY, + true, sElapsedRealtimeClock.millis()); + } else { + // This network doesn't satisfy transport affinities. Check if any other + // available networks do satisfy the affinities before saying that the + // transport affinity is no longer satisfied for flex. + boolean isTransportAffinitySatisfied = false; + for (int i = mAvailableNetworks.size() - 1; i >= 0; --i) { + final CachedNetworkMetadata cnm = mAvailableNetworks.valueAt(i); + if (cnm != null && cnm.satisfiesTransportAffinities) { + isTransportAffinitySatisfied = true; + break; + } + } + if (!isTransportAffinitySatisfied) { + mFlexibilityController.setConstraintSatisfied( + JobStatus.CONSTRAINT_CONNECTIVITY, false, + sElapsedRealtimeClock.millis()); + } + } + } }; private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 70f9a52f3299..fed3c42ab87f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -43,6 +43,8 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; +import android.util.SparseLongArray; +import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -68,11 +70,6 @@ public final class FlexibilityController extends StateController { | CONSTRAINT_CHARGING | CONSTRAINT_IDLE; - /** List of flexible constraints a job can opt into. */ - static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW - | CONSTRAINT_CHARGING - | CONSTRAINT_IDLE; - /** List of all job flexible constraints whose satisfaction is job specific. */ private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY; @@ -83,9 +80,6 @@ public final class FlexibilityController extends StateController { private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS); - static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS = - Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS); - static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); @@ -103,6 +97,9 @@ public final class FlexibilityController extends StateController { private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; + private long mUnseenConstraintGracePeriodMs = + FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; + @VisibleForTesting @GuardedBy("mLock") boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED; @@ -132,6 +129,9 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") int mSatisfiedFlexibleConstraints; + @GuardedBy("mLock") + private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray(); + @VisibleForTesting @GuardedBy("mLock") final FlexibilityTracker mFlexibilityTracker; @@ -258,25 +258,68 @@ public final class FlexibilityController extends StateController { boolean isFlexibilitySatisfiedLocked(JobStatus js) { return !mFlexibilityEnabled || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP - || getNumSatisfiedRequiredConstraintsLocked(js) - >= js.getNumRequiredFlexibleConstraints() + || hasEnoughSatisfiedConstraintsLocked(js) || mService.isCurrentlyRunningLocked(js); } + /** + * Returns whether there are enough constraints satisfied to allow running the job from flex's + * perspective. This takes into account unseen constraint combinations and expectations around + * whether additional constraints can ever be satisfied. + */ @VisibleForTesting @GuardedBy("mLock") - int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) { - return Integer.bitCount(mSatisfiedFlexibleConstraints) - // Connectivity is job-specific, so must be handled separately. - + (js.canApplyTransportAffinities() - && js.areTransportAffinitiesSatisfied() ? 1 : 0); + boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) { + final int satisfiedConstraints = mSatisfiedFlexibleConstraints + & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0)); + final int numSatisfied = Integer.bitCount(satisfiedConstraints); + if (numSatisfied >= js.getNumRequiredFlexibleConstraints()) { + return true; + } + // We don't yet have the full number of required flex constraints. See if we should expect + // to be able to reach it. If not, then there's no point waiting anymore. + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (nowElapsed < mUnseenConstraintGracePeriodMs) { + // Too soon after boot. Not enough time to start predicting. Wait longer. + return false; + } + + // The intention is to not force jobs to wait for constraint combinations that have never + // been seen together in a while. The job may still be allowed to wait for other constraint + // combinations. Thus, the logic is: + // If all the constraint combinations that have a count higher than the current satisfied + // count have not been seen recently enough, then assume they won't be seen anytime soon, + // so don't force the job to wait longer. If any combinations with a higher count have been + // seen recently, then the job can potentially wait for those combinations. + final int irrelevantConstraints = ~(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0)); + for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) { + final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); + if ((constraints & irrelevantConstraints) != 0) { + // Ignore combinations that couldn't satisfy this job's needs. + continue; + } + final long lastSeenElapsed = mLastSeenConstraintTimesElapsed.valueAt(i); + final boolean seenRecently = + nowElapsed - lastSeenElapsed <= mUnseenConstraintGracePeriodMs; + if (Integer.bitCount(constraints) > numSatisfied && seenRecently) { + // We've seen a set of constraints with a higher count than what is currently + // satisfied recently enough, which means we can expect to see it again at some + // point. Keep waiting for now. + return false; + } + } + + // We haven't seen any constraint set with more satisfied than the current satisfied count. + // There's no reason to expect additional constraints to be satisfied. Let the job run. + return true; } /** * Sets the controller's constraint to a given state. * Changes flexibility constraint satisfaction for affected jobs. */ - @VisibleForTesting void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) { synchronized (mLock) { final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; @@ -286,14 +329,34 @@ public final class FlexibilityController extends StateController { if (DEBUG) { Slog.d(TAG, "setConstraintSatisfied: " - + " constraint: " + constraint + " state: " + state); + + " constraint: " + constraint + " state: " + state); + } + + // Mark now as the last time we saw this set of constraints. + mLastSeenConstraintTimesElapsed.put(mSatisfiedFlexibleConstraints, nowElapsed); + if (!state) { + // Mark now as the last time we saw this particular constraint. + // (Good for logging/dump purposes). + mLastSeenConstraintTimesElapsed.put(constraint, nowElapsed); } mSatisfiedFlexibleConstraints = (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0); - // Push the job update to the handler to avoid blocking other controllers and - // potentially batch back-to-back controller state updates together. - mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); + + if ((JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS & constraint) != 0) { + // Job-specific constraint --> don't need to proceed with logic below that + // works with system-wide constraints. + return; + } + + if (mFlexibilityEnabled) { + // Only attempt to update jobs if the flex logic is enabled. Otherwise, the status + // of the jobs won't change, so all the work will be a waste. + + // Push the job update to the handler to avoid blocking other controllers and + // potentially batch back-to-back controller state updates together. + mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); + } } } @@ -543,7 +606,6 @@ public final class FlexibilityController extends StateController { if (!predicate.test(js)) { continue; } - pw.print("#"); js.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, js.getSourceUid()); @@ -645,7 +707,7 @@ public final class FlexibilityController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); - for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) { + for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) { final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker .getJobsByNumRequiredConstraints(o); @@ -687,6 +749,8 @@ public final class FlexibilityController extends StateController { FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; + static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = + FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms"; static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; @VisibleForTesting @@ -698,6 +762,8 @@ public final class FlexibilityController extends StateController { final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS; + @VisibleForTesting + static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS; /** * If false the controller will not track new jobs @@ -717,6 +783,11 @@ public final class FlexibilityController extends StateController { public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; /** The max deadline for rescheduled jobs. */ public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; + /** + * How long to wait after last seeing a constraint combination before no longer waiting for + * it in order to run jobs. + */ + public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; @GuardedBy("mLock") public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @@ -780,6 +851,14 @@ public final class FlexibilityController extends StateController { mShouldReevaluateConstraints = true; } break; + case KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS: + UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = + properties.getLong(key, DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS); + if (mUnseenConstraintGracePeriodMs != UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) { + mUnseenConstraintGracePeriodMs = UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; + mShouldReevaluateConstraints = true; + } + break; case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS: String dropPercentString = properties.getString(key, ""); PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = @@ -834,6 +913,8 @@ public final class FlexibilityController extends StateController { PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println(); pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); + pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) + .println(); pw.decreaseIndent(); } @@ -854,12 +935,34 @@ public final class FlexibilityController extends StateController { @Override @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { - pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)); - pw.print("Satisfied Flexible Constraints: "); + pw.print("Satisfied Flexible Constraints:"); JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); pw.println(); pw.println(); + final long nowElapsed = sElapsedRealtimeClock.millis(); + pw.println("Time since constraint combos last seen:"); + pw.increaseIndent(); + for (int i = 0; i < mLastSeenConstraintTimesElapsed.size(); ++i) { + final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); + if (constraints == mSatisfiedFlexibleConstraints) { + pw.print("0ms"); + } else { + TimeUtils.formatDuration( + mLastSeenConstraintTimesElapsed.valueAt(i), nowElapsed, pw); + } + pw.print(":"); + if (constraints != 0) { + // dumpConstraints prepends with a space, so no need to add a space after the : + JobStatus.dumpConstraints(pw, constraints); + } else { + pw.print(" none"); + } + pw.println(); + } + pw.decreaseIndent(); + + pw.println(); mFlexibilityTracker.dump(pw, predicate); pw.println(); mFlexibilityAlarmQueue.dump(pw); 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 d6ada4cd7fdc..13bea6bd1dd1 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 @@ -598,7 +598,6 @@ public final class JobStatus { long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs, int internalFlags, int dynamicConstraints) { - this.job = job; this.callingUid = callingUid; this.standbyBucket = standbyBucket; mNamespace = namespace; @@ -626,6 +625,22 @@ public final class JobStatus { this.sourceTag = tag; } + // This needs to be done before setting the field variable. + if (job.getRequiredNetwork() != null) { + // Later, when we check if a given network satisfies the required + // network, we need to know the UID that is requesting it, so push + // the source UID into place. + final JobInfo.Builder builder = new JobInfo.Builder(job); + builder.setRequiredNetwork(new NetworkRequest.Builder(job.getRequiredNetwork()) + .setUids(Collections.singleton(new Range<>(this.sourceUid, this.sourceUid))) + .build()); + // Don't perform validation checks at this point since we've already passed the + // initial validation check. + job = builder.build(false, false); + } + + this.job = job; + final String bnNamespace = namespace == null ? "" : "@" + namespace + "@"; this.batteryName = this.sourceTag != null ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName() @@ -708,21 +723,6 @@ public final class JobStatus { updateNetworkBytesLocked(); - if (job.getRequiredNetwork() != null) { - // Later, when we check if a given network satisfies the required - // network, we need to know the UID that is requesting it, so push - // our source UID into place. - final JobInfo.Builder builder = new JobInfo.Builder(job); - final NetworkRequest.Builder requestBuilder = - new NetworkRequest.Builder(job.getRequiredNetwork()); - requestBuilder.setUids( - Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid))); - builder.setRequiredNetwork(requestBuilder.build()); - // Don't perform validation checks at this point since we've already passed the - // initial validation check. - job = builder.build(false, false); - } - updateMediaBackupExemptionStatus(); } @@ -1054,6 +1054,12 @@ public final class JobStatus { return mLoggingJobId; } + /** Returns a trace tag using debug information provided by the app. */ + @Nullable + public String getAppTraceTag() { + return job.getTraceTag(); + } + /** Returns whether this job was scheduled by one app on behalf of another. */ public boolean isProxyJob() { return mIsProxyJob; @@ -2763,6 +2769,15 @@ public final class JobStatus { pw.println("Has late constraint"); } + if (job.getTraceTag() != null) { + pw.print("Trace tag: "); + pw.println(job.getTraceTag()); + } + if (job.getDebugTags().size() > 0) { + pw.print("Debug tags: "); + pw.println(job.getDebugTags()); + } + pw.decreaseIndent(); } diff --git a/api/Android.bp b/api/Android.bp index 4d56b3748881..d6c14fbdfae3 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -80,7 +80,9 @@ combined_apis { "framework-location", "framework-media", "framework-mediaprovider", + "framework-nfc", "framework-ondevicepersonalization", + "framework-pdf", "framework-permission", "framework-permission-s", "framework-scheduling", @@ -383,7 +385,10 @@ java_defaults { stub_only_libs: ["framework-protos"], impl_only_libs: ["framework-minus-apex-headers"], // the framework, including hidden API impl_library_visibility: ["//frameworks/base"], - defaults_visibility: ["//frameworks/base/location"], + defaults_visibility: [ + "//frameworks/base/location", + "//frameworks/base/nfc", + ], plugins: ["error_prone_android_framework"], errorprone: { javacflags: [ diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index f6f69291ce0e..28b2d4b5e3ee 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -635,6 +635,7 @@ java_defaults { api_contributions: [ "framework-virtualization.stubs.source.test.api.contribution", "framework-location.stubs.source.test.api.contribution", + "framework-nfc.stubs.source.test.api.contribution", ], } diff --git a/api/api.go b/api/api.go index 8df6dab715ef..71b1e10d2f47 100644 --- a/api/api.go +++ b/api/api.go @@ -32,6 +32,7 @@ const conscrypt = "conscrypt.module.public.api" const i18n = "i18n.module.public.api" const virtualization = "framework-virtualization" const location = "framework-location" +const nfc = "framework-nfc" var core_libraries_modules = []string{art, conscrypt, i18n} @@ -43,7 +44,7 @@ var core_libraries_modules = []string{art, conscrypt, i18n} // APIs. // In addition, the modules in this list are allowed to contribute to test APIs // stubs. -var non_updatable_modules = []string{virtualization, location} +var non_updatable_modules = []string{virtualization, location, nfc} // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp index 7659054119c8..ec2b1f4db521 100644 --- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp +++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp @@ -283,7 +283,10 @@ static void configure(JNIEnv* env, jclass /* clazz */, jint handle, jint code, std::vector<int32_t> configs = toVector(env, rawConfigs); // Configure uinput device, with user specified code and value. for (auto& config : configs) { - ::ioctl(static_cast<int>(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config); + if (::ioctl(static_cast<int>(handle), _IOW(UINPUT_IOCTL_BASE, code, int), config) < 0) { + ALOGE("Error configuring device (ioctl %d, value 0x%x): %s", code, config, + strerror(errno)); + } } } diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java index ad5e70f4ff0c..b0fa34c68092 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Device.java +++ b/cmds/uinput/src/com/android/commands/uinput/Device.java @@ -160,9 +160,16 @@ public class Device { switch (msg.what) { case MSG_OPEN_UINPUT_DEVICE: SomeArgs args = (SomeArgs) msg.obj; - mPtr = nativeOpenUinputDevice((String) args.arg1, args.argi1, args.argi2, + String name = (String) args.arg1; + mPtr = nativeOpenUinputDevice(name, args.argi1, args.argi2, args.argi3, args.argi4, args.argi5, (String) args.arg2, new DeviceCallback()); + if (mPtr == 0) { + RuntimeException ex = new RuntimeException( + "Could not create uinput device \"" + name + "\""); + Log.e(TAG, "Couldn't create uinput device, exiting.", ex); + throw ex; + } break; case MSG_INJECT_EVENT: if (mPtr != 0) { diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 01486c0708ce..4498bc2a09d6 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -30,7 +30,7 @@ import src.com.android.commands.uinput.InputAbsInfo; public class Event { private static final String TAG = "UinputEvent"; - enum Command { + public enum Command { REGISTER, DELAY, INJECT, @@ -188,8 +188,8 @@ public class Event { mEvent.mId = id; } - public void setCommand(String command) { - mEvent.mCommand = Command.valueOf(command.toUpperCase()); + public void setCommand(Command command) { + mEvent.mCommand = command; } public void setName(String name) { diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java index 53d0be819dae..a2195c7809be 100644 --- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -57,7 +57,8 @@ public class JsonStyleParser { String name = mReader.nextName(); switch (name) { case "id" -> eb.setId(readInt()); - case "command" -> eb.setCommand(mReader.nextString()); + case "command" -> eb.setCommand( + Event.Command.valueOf(mReader.nextString().toUpperCase())); case "name" -> eb.setName(mReader.nextString()); case "vid" -> eb.setVid(readInt()); case "pid" -> eb.setPid(readInt()); diff --git a/core/api/current.txt b/core/api/current.txt index b9719e10ea02..207abb216c37 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8859,6 +8859,7 @@ package android.app.job { method public int getBackoffPolicy(); method @Nullable public android.content.ClipData getClipData(); method public int getClipGrantFlags(); + method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public java.util.Set<java.lang.String> getDebugTags(); method public long getEstimatedNetworkDownloadBytes(); method public long getEstimatedNetworkUploadBytes(); method @NonNull public android.os.PersistableBundle getExtras(); @@ -8875,6 +8876,7 @@ package android.app.job { method public int getPriority(); method @Nullable public android.net.NetworkRequest getRequiredNetwork(); method @NonNull public android.content.ComponentName getService(); + method @FlaggedApi("android.app.job.job_debug_info_apis") @Nullable public String getTraceTag(); method @NonNull public android.os.Bundle getTransientExtras(); method public long getTriggerContentMaxDelay(); method public long getTriggerContentUpdateDelay(); @@ -8911,8 +8913,10 @@ package android.app.job { public static final class JobInfo.Builder { ctor public JobInfo.Builder(int, @NonNull android.content.ComponentName); + method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder addDebugTag(@NonNull String); method public android.app.job.JobInfo.Builder addTriggerContentUri(@NonNull android.app.job.JobInfo.TriggerContentUri); method public android.app.job.JobInfo build(); + method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder removeDebugTag(@NonNull String); method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int); method public android.app.job.JobInfo.Builder setClipData(@Nullable android.content.ClipData, int); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long); @@ -8933,6 +8937,7 @@ package android.app.job { method public android.app.job.JobInfo.Builder setRequiresCharging(boolean); method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean); method public android.app.job.JobInfo.Builder setRequiresStorageNotLow(boolean); + method @FlaggedApi("android.app.job.job_debug_info_apis") @NonNull public android.app.job.JobInfo.Builder setTraceTag(@Nullable String); method public android.app.job.JobInfo.Builder setTransientExtras(@NonNull android.os.Bundle); method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long); method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long); @@ -33152,7 +33157,6 @@ package android.os { public static class PerformanceHintManager.Session implements java.io.Closeable { method public void close(); method public void reportActualWorkDuration(long); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration); method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean); method public void setThreads(@NonNull int[]); method public void updateTargetWorkDuration(long); @@ -33495,7 +33499,6 @@ package android.os { method public static boolean setCurrentTimeMillis(long); method public static void sleep(long); method public static long uptimeMillis(); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos(); } public class TestLooperManager { @@ -33761,22 +33764,6 @@ package android.os { method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes); } - @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable { - ctor public WorkDuration(); - ctor public WorkDuration(long, long, long, long); - method public int describeContents(); - method public long getActualCpuDurationNanos(); - method public long getActualGpuDurationNanos(); - method public long getActualTotalDurationNanos(); - method public long getWorkPeriodStartTimestampNanos(); - method public void setActualCpuDurationNanos(long); - method public void setActualGpuDurationNanos(long); - method public void setActualTotalDurationNanos(long); - method public void setWorkPeriodStartTimestampNanos(long); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR; - } - public class WorkSource implements android.os.Parcelable { ctor public WorkSource(); ctor public WorkSource(android.os.WorkSource); diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index 449249e02768..f331e7f5fa84 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -389,6 +389,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 +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: + Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. + + RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler): Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler): diff --git a/core/api/removed.txt b/core/api/removed.txt index 989bb7756e4c..285dcc6a3ed9 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -112,6 +112,9 @@ package android.content.pm { method public abstract boolean setInstantAppCookie(@Nullable byte[]); } + @IntDef(prefix={"FLAG_PERMISSION_"}, value={0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x100, 0x200, 0x2000, 0x1000, 0x800, 0x4000, 0x8000, 0x8, 0x10000, 0x20000}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + } + public final class SharedLibraryInfo implements android.os.Parcelable { method public boolean isBuiltin(); method public boolean isDynamic(); @@ -318,6 +321,9 @@ package android.os { method public CharSequence getBadgedLabelForUser(CharSequence, android.os.UserHandle); } + @IntDef(flag=true, prefix={"RESTRICTION_"}, value={0x0, 0x1, 0x2, 0x4}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource { + } + } package android.os.storage { @@ -493,6 +499,13 @@ package android.telephony { } +package android.telephony.euicc { + + @IntDef(prefix={"EUICC_OTA_"}, value={0x1, 0x2, 0x3, 0x4, 0x5}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus { + } + +} + package android.text.format { public class DateFormat { @@ -554,6 +567,9 @@ package android.view { field public static final int TYPE_STATUS_BAR_PANEL = 2014; // 0x7de } + @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={0x80000, 0x10}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags { + } + } package android.view.accessibility { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5958f87e5635..32d252ebda29 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3861,11 +3861,15 @@ package android.content.pm { field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } + public class PackageInfo implements android.os.Parcelable { + method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis(); + } + public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; @@ -3877,6 +3881,7 @@ package android.content.pm { field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 @@ -3933,6 +3938,7 @@ package android.content.pm { method public void setRequestDowngrade(boolean); method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged(); + method @FlaggedApi("android.content.pm.archiving") public void setUnarchiveId(int); } public class PackageItemInfo { @@ -3966,7 +3972,7 @@ package android.content.pm { method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public String getPermissionControllerPackageName(); - method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]); method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -3997,7 +4003,7 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean); method @NonNull public boolean shouldShowNewAppInstalledNotification(); method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int); - method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>); field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS"; field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER"; @@ -4114,9 +4120,7 @@ package android.content.pm { public static interface PackageManager.OnPermissionsChangedListener { method public void onPermissionsChanged(int); - } - - @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String); } public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable { @@ -4620,7 +4624,7 @@ package android.hardware.hdmi { } public static interface HdmiClient.OnDeviceSelectedListener { - method public void onDeviceSelected(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int, int); + method public void onDeviceSelected(int, int); } public final class HdmiControlManager { @@ -4818,9 +4822,6 @@ package android.hardware.hdmi { method public void onChange(@NonNull String); } - @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult { - } - public static interface HdmiControlManager.HotplugEventListener { method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent); } @@ -4967,7 +4968,7 @@ package android.hardware.hdmi { } public static interface HdmiSwitchClient.OnSelectListener { - method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int); + method public void onSelect(int); } public class HdmiTimerRecordSources { @@ -5684,14 +5685,14 @@ package android.hardware.radio { } public final class ProgramSelector implements android.os.Parcelable { - ctor public ProgramSelector(@android.hardware.radio.ProgramSelector.ProgramType int, @NonNull android.hardware.radio.ProgramSelector.Identifier, @Nullable android.hardware.radio.ProgramSelector.Identifier[], @Nullable long[]); - method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(@android.hardware.radio.RadioManager.Band int, int); - method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(@android.hardware.radio.RadioManager.Band int, int, int); + ctor public ProgramSelector(int, @NonNull android.hardware.radio.ProgramSelector.Identifier, @Nullable android.hardware.radio.ProgramSelector.Identifier[], @Nullable long[]); + method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); + method @NonNull public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int, int); method public int describeContents(); - method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(@android.hardware.radio.ProgramSelector.IdentifierType int); - method public long getFirstId(@android.hardware.radio.ProgramSelector.IdentifierType int); + method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(int); + method public long getFirstId(int); method @NonNull public android.hardware.radio.ProgramSelector.Identifier getPrimaryId(); - method @Deprecated @android.hardware.radio.ProgramSelector.ProgramType public int getProgramType(); + method @Deprecated public int getProgramType(); method @NonNull public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds(); method @Deprecated @NonNull public long[] getVendorIds(); method @NonNull public android.hardware.radio.ProgramSelector withSecondaryPreferred(@NonNull android.hardware.radio.ProgramSelector.Identifier); @@ -5740,21 +5741,15 @@ package android.hardware.radio { } public static final class ProgramSelector.Identifier implements android.os.Parcelable { - ctor public ProgramSelector.Identifier(@android.hardware.radio.ProgramSelector.IdentifierType int, long); + ctor public ProgramSelector.Identifier(int, long); method public int describeContents(); - method @android.hardware.radio.ProgramSelector.IdentifierType public int getType(); + method public int getType(); method public long getValue(); method public boolean isCategoryType(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR; } - @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { - } - - @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType { - } - public class RadioManager { method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull java.util.Set<java.lang.Integer>, @NonNull android.hardware.radio.Announcement.OnListUpdatedListener); method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void addAnnouncementListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.Set<java.lang.Integer>, @NonNull android.hardware.radio.Announcement.OnListUpdatedListener); @@ -5812,9 +5807,6 @@ package android.hardware.radio { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandDescriptor> CREATOR; } - @IntDef(prefix={"BAND_"}, value={android.hardware.radio.RadioManager.BAND_INVALID, android.hardware.radio.RadioManager.BAND_AM, android.hardware.radio.RadioManager.BAND_FM, android.hardware.radio.RadioManager.BAND_AM_HD, android.hardware.radio.RadioManager.BAND_FM_HD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RadioManager.Band { - } - public static class RadioManager.BandConfig implements android.os.Parcelable { method public int describeContents(); method public int getLowerLimit(); @@ -5885,8 +5877,8 @@ package android.hardware.radio { method public boolean isBackgroundScanningSupported(); method public boolean isCaptureSupported(); method public boolean isInitializationRequired(); - method public boolean isProgramIdentifierSupported(@android.hardware.radio.ProgramSelector.IdentifierType int); - method public boolean isProgramTypeSupported(@android.hardware.radio.ProgramSelector.ProgramType int); + method public boolean isProgramIdentifierSupported(int); + method public boolean isProgramTypeSupported(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.ModuleProperties> CREATOR; } @@ -10570,7 +10562,7 @@ package android.os { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle); - method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getUserSwitchability(); method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers(); @@ -10630,14 +10622,11 @@ package android.os { public static final class UserManager.EnforcingUser implements android.os.Parcelable { method public int describeContents(); method public android.os.UserHandle getUserHandle(); - method @android.os.UserManager.UserRestrictionSource public int getUserRestrictionSource(); + method public int getUserRestrictionSource(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.UserManager.EnforcingUser> CREATOR; } - @IntDef(flag=true, prefix={"RESTRICTION_"}, value={android.os.UserManager.RESTRICTION_NOT_SET, android.os.UserManager.RESTRICTION_SOURCE_SYSTEM, android.os.UserManager.RESTRICTION_SOURCE_DEVICE_OWNER, android.os.UserManager.RESTRICTION_SOURCE_PROFILE_OWNER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource { - } - public abstract class Vibrator { method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener); method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener); @@ -11941,13 +11930,13 @@ package android.service.euicc { method public android.service.carrier.CarrierIdentifier getCarrierIdentifier(); method public String getIccid(); method @Nullable public String getNickname(); - method @android.service.euicc.EuiccProfileInfo.PolicyRule public int getPolicyRules(); - method @android.service.euicc.EuiccProfileInfo.ProfileClass public int getProfileClass(); + method public int getPolicyRules(); + method public int getProfileClass(); method public String getProfileName(); method public String getServiceProviderName(); - method @android.service.euicc.EuiccProfileInfo.ProfileState public int getState(); + method public int getState(); method @Nullable public java.util.List<android.telephony.UiccAccessRule> getUiccAccessRules(); - method public boolean hasPolicyRule(@android.service.euicc.EuiccProfileInfo.PolicyRule int); + method public boolean hasPolicyRule(int); method public boolean hasPolicyRules(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.euicc.EuiccProfileInfo> CREATOR; @@ -11968,23 +11957,14 @@ package android.service.euicc { method public android.service.euicc.EuiccProfileInfo.Builder setCarrierIdentifier(android.service.carrier.CarrierIdentifier); method public android.service.euicc.EuiccProfileInfo.Builder setIccid(String); method public android.service.euicc.EuiccProfileInfo.Builder setNickname(String); - method public android.service.euicc.EuiccProfileInfo.Builder setPolicyRules(@android.service.euicc.EuiccProfileInfo.PolicyRule int); - method public android.service.euicc.EuiccProfileInfo.Builder setProfileClass(@android.service.euicc.EuiccProfileInfo.ProfileClass int); + method public android.service.euicc.EuiccProfileInfo.Builder setPolicyRules(int); + method public android.service.euicc.EuiccProfileInfo.Builder setProfileClass(int); method public android.service.euicc.EuiccProfileInfo.Builder setProfileName(String); method public android.service.euicc.EuiccProfileInfo.Builder setServiceProviderName(String); - method public android.service.euicc.EuiccProfileInfo.Builder setState(@android.service.euicc.EuiccProfileInfo.ProfileState int); + method public android.service.euicc.EuiccProfileInfo.Builder setState(int); method public android.service.euicc.EuiccProfileInfo.Builder setUiccAccessRule(@Nullable java.util.List<android.telephony.UiccAccessRule>); } - @IntDef(flag=true, prefix={"POLICY_RULE_"}, value={android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DISABLE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DELETE_AFTER_DISABLING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.PolicyRule { - } - - @IntDef(prefix={"PROFILE_CLASS_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_TESTING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_PROVISIONING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileClass { - } - - @IntDef(prefix={"PROFILE_STATE_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_STATE_DISABLED, android.service.euicc.EuiccProfileInfo.PROFILE_STATE_ENABLED, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileState { - } - public abstract class EuiccService extends android.app.Service { ctor public EuiccService(); method public void dump(@NonNull java.io.PrintWriter); @@ -11995,14 +11975,14 @@ package android.service.euicc { method @NonNull public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @NonNull android.os.Bundle); method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean); method @Deprecated public abstract int onEraseSubscriptions(int); - method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int); + method public int onEraseSubscriptions(int, int); method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean); method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean); method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean); method public abstract String onGetEid(int); method @NonNull public abstract android.telephony.euicc.EuiccInfo onGetEuiccInfo(int); method @NonNull public abstract android.service.euicc.GetEuiccProfileInfoListResult onGetEuiccProfileInfoList(int); - method @android.telephony.euicc.EuiccManager.OtaStatus public abstract int onGetOtaStatus(int); + method public abstract int onGetOtaStatus(int); method public abstract int onRetainSubscriptionsForFactoryReset(int); method public abstract void onStartOtaIfNecessary(int, android.service.euicc.EuiccService.OtaStatusChangedCallback); method @Deprecated public abstract int onSwitchToSubscription(int, @Nullable String, boolean); @@ -12266,7 +12246,7 @@ package android.service.persistentdata { public class PersistentDataBlockManager { method @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize(); - method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); + method @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName(); @@ -12279,9 +12259,6 @@ package android.service.persistentdata { field public static final int FLASH_LOCK_UNLOCKED = 0; // 0x0 } - @IntDef(prefix={"FLASH_LOCK_"}, value={android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNKNOWN, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_LOCKED, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNLOCKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PersistentDataBlockManager.FlashLockState { - } - } package android.service.quicksettings { @@ -15073,10 +15050,10 @@ package android.telephony.euicc { public class EuiccCardManager { method public void authenticateServer(String, String, byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); - method public void cancelSession(String, byte[], @android.telephony.euicc.EuiccCardManager.CancelReason int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); + method public void cancelSession(String, byte[], int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void deleteProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); - method public void listNotifications(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); + method public void listNotifications(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); method public void loadBoundProfilePackage(String, byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void prepareDownload(String, @Nullable byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void removeNotificationFromList(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); @@ -15089,9 +15066,9 @@ package android.telephony.euicc { method public void requestProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>); method public void requestRulesAuthTable(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccRulesAuthTable>); method public void requestSmdsAddress(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.String>); - method public void resetMemory(String, @android.telephony.euicc.EuiccCardManager.ResetOption int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); + method public void resetMemory(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method public void retrieveNotification(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification>); - method public void retrieveNotificationList(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); + method public void retrieveNotificationList(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); method public void setDefaultSmdpAddress(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method public void setNickname(String, String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method @Deprecated public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>); @@ -15111,12 +15088,6 @@ package android.telephony.euicc { field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff } - @IntDef(prefix={"CANCEL_REASON_"}, value={android.telephony.euicc.EuiccCardManager.CANCEL_REASON_END_USER_REJECTED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_POSTPONED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_TIMEOUT, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_PPR_NOT_ALLOWED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.CancelReason { - } - - @IntDef(flag=true, prefix={"RESET_OPTION_"}, value={android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.ResetOption { - } - public static interface EuiccCardManager.ResultCallback<T> { method public void onComplete(int, T); } @@ -15124,7 +15095,7 @@ package android.telephony.euicc { public class EuiccManager { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(android.content.Intent, android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@NonNull android.app.PendingIntent); - method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@android.telephony.euicc.EuiccCardManager.ResetOption int, @NonNull android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(int, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); @@ -15159,18 +15130,15 @@ package android.telephony.euicc { field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; } - @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus { - } - public final class EuiccNotification implements android.os.Parcelable { - ctor public EuiccNotification(int, String, @android.telephony.euicc.EuiccNotification.Event int, @Nullable byte[]); + ctor public EuiccNotification(int, String, int, @Nullable byte[]); method public int describeContents(); method @Nullable public byte[] getData(); - method @android.telephony.euicc.EuiccNotification.Event public int getEvent(); + method public int getEvent(); method public int getSeq(); method public String getTargetAddr(); method public void writeToParcel(android.os.Parcel, int); - field @android.telephony.euicc.EuiccNotification.Event public static final int ALL_EVENTS = 15; // 0xf + field public static final int ALL_EVENTS = 15; // 0xf field @NonNull public static final android.os.Parcelable.Creator<android.telephony.euicc.EuiccNotification> CREATOR; field public static final int EVENT_DELETE = 8; // 0x8 field public static final int EVENT_DISABLE = 4; // 0x4 @@ -15178,13 +15146,10 @@ package android.telephony.euicc { field public static final int EVENT_INSTALL = 1; // 0x1 } - @IntDef(flag=true, prefix={"EVENT_"}, value={android.telephony.euicc.EuiccNotification.EVENT_INSTALL, android.telephony.euicc.EuiccNotification.EVENT_ENABLE, android.telephony.euicc.EuiccNotification.EVENT_DISABLE, android.telephony.euicc.EuiccNotification.EVENT_DELETE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccNotification.Event { - } - public final class EuiccRulesAuthTable implements android.os.Parcelable { method public int describeContents(); - method public int findIndex(@android.service.euicc.EuiccProfileInfo.PolicyRule int, android.service.carrier.CarrierIdentifier); - method public boolean hasPolicyRuleFlag(int, @android.telephony.euicc.EuiccRulesAuthTable.PolicyRuleFlag int); + method public int findIndex(int, android.service.carrier.CarrierIdentifier); + method public boolean hasPolicyRuleFlag(int, int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.euicc.EuiccRulesAuthTable> CREATOR; field public static final int POLICY_RULE_FLAG_CONSENT_REQUIRED = 1; // 0x1 @@ -15196,9 +15161,6 @@ package android.telephony.euicc { method public android.telephony.euicc.EuiccRulesAuthTable build(); } - @IntDef(flag=true, prefix={"POLICY_RULE_FLAG_"}, value={android.telephony.euicc.EuiccRulesAuthTable.POLICY_RULE_FLAG_CONSENT_REQUIRED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccRulesAuthTable.PolicyRuleFlag { - } - } package android.telephony.gba { @@ -17098,7 +17060,7 @@ package android.view { } public abstract class Window { - method public void addSystemFlags(@android.view.WindowManager.LayoutParams.SystemFlags int); + method public void addSystemFlags(int); } public interface WindowManager extends android.view.ViewManager { @@ -17117,9 +17079,6 @@ package android.view { field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10 } - @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags { - } - } package android.view.accessibility { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 865240207a95..dec1ee52712d 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -517,6 +517,18 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): Methods must not throw generic exceptions (`java.lang.Throwable`) +InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0: + Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +InvalidNullabilityOverride: android.service.voice.HotwordDetectionService#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.service.voice.VisualQueryDetectionService#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.service.voice.VisualQueryDetectionService#openFileInput(String): + Invalid nullability on method `openFileInput` return. Overrides of unannotated super method cannot be Nullable. +InvalidNullabilityOverride: android.service.voice.VisualQueryDetectionService#openFileInput(String) parameter #0: + Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. + + KotlinKeyword: android.app.Notification#when: Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 402a7184c7c2..51b8a11e1791 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -112,6 +112,22 @@ package android.hardware.hdmi { method @Deprecated public void requestRemoteDeviceToBecomeActiveSource(@NonNull android.hardware.hdmi.HdmiDeviceInfo); } + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult { + } + +} + +package android.hardware.radio { + + @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { + } + + @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType { + } + + @IntDef(prefix={"BAND_"}, value={android.hardware.radio.RadioManager.BAND_INVALID, android.hardware.radio.RadioManager.BAND_AM, android.hardware.radio.RadioManager.BAND_FM, android.hardware.radio.RadioManager.BAND_AM_HD, android.hardware.radio.RadioManager.BAND_FM_HD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RadioManager.Band { + } + } package android.media.tv { @@ -145,6 +161,19 @@ package android.os { } +package android.service.euicc { + + @IntDef(flag=true, prefix={"POLICY_RULE_"}, value={android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DISABLE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE, android.service.euicc.EuiccProfileInfo.POLICY_RULE_DELETE_AFTER_DISABLING}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.PolicyRule { + } + + @IntDef(prefix={"PROFILE_CLASS_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_TESTING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_PROVISIONING, android.service.euicc.EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileClass { + } + + @IntDef(prefix={"PROFILE_STATE_"}, value={android.service.euicc.EuiccProfileInfo.PROFILE_STATE_DISABLED, android.service.euicc.EuiccProfileInfo.PROFILE_STATE_ENABLED, 0xffffffff}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccProfileInfo.ProfileState { + } + +} + package android.service.notification { public abstract class NotificationListenerService extends android.app.Service { @@ -165,6 +194,13 @@ package android.service.notification { } +package android.service.persistentdata { + + @IntDef(prefix={"FLASH_LOCK_"}, value={android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNKNOWN, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_LOCKED, android.service.persistentdata.PersistentDataBlockManager.FLASH_LOCK_UNLOCKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PersistentDataBlockManager.FlashLockState { + } + +} + package android.service.search { public abstract class SearchUiService extends android.app.Service { @@ -213,6 +249,22 @@ package android.telephony.data { } +package android.telephony.euicc { + + @IntDef(prefix={"CANCEL_REASON_"}, value={android.telephony.euicc.EuiccCardManager.CANCEL_REASON_END_USER_REJECTED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_POSTPONED, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_TIMEOUT, android.telephony.euicc.EuiccCardManager.CANCEL_REASON_PPR_NOT_ALLOWED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.CancelReason { + } + + @IntDef(flag=true, prefix={"RESET_OPTION_"}, value={android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES, android.telephony.euicc.EuiccCardManager.RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccCardManager.ResetOption { + } + + @IntDef(flag=true, prefix={"EVENT_"}, value={android.telephony.euicc.EuiccNotification.EVENT_INSTALL, android.telephony.euicc.EuiccNotification.EVENT_ENABLE, android.telephony.euicc.EuiccNotification.EVENT_DISABLE, android.telephony.euicc.EuiccNotification.EVENT_DELETE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccNotification.Event { + } + + @IntDef(flag=true, prefix={"POLICY_RULE_FLAG_"}, value={android.telephony.euicc.EuiccRulesAuthTable.POLICY_RULE_FLAG_CONSENT_REQUIRED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccRulesAuthTable.PolicyRuleFlag { + } + +} + package android.telephony.ims { public interface DelegateStateCallback { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 3a91e25de9e6..bf26bd0a0ec6 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -511,6 +511,16 @@ 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 +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: + Invalid nullability on parameter `configuration` in method `onConfigurationChanged`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +InvalidNullabilityOverride: android.window.WindowProviderService#registerComponentCallbacks(android.content.ComponentCallbacks) parameter #0: + Invalid nullability on parameter `callback` in method `registerComponentCallbacks`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +InvalidNullabilityOverride: android.window.WindowProviderService#unregisterComponentCallbacks(android.content.ComponentCallbacks) parameter #0: + Invalid nullability on parameter `callback` in method `unregisterComponentCallbacks`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. + + KotlinKeyword: android.app.Notification#when: Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords diff --git a/core/java/Android.bp b/core/java/Android.bp index 48cafc596d87..dfe3344a466a 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -413,6 +413,10 @@ aidl_interface { backend: { rust: { enabled: true, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], }, }, } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c136db68fd25..02eaf0b3bbd3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -56,7 +56,7 @@ import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupAnnotations.OperationType; import android.app.compat.CompatChanges; import android.app.sdksandbox.sandboxactivity.ActivityContextInfo; -import android.app.sdksandbox.sandboxactivity.ActivityContextInfoProvider; +import android.app.sdksandbox.sandboxactivity.SdkSandboxActivityAuthority; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; @@ -3795,8 +3795,10 @@ public final class ActivityThread extends ClientTransactionHandler r.activityInfo.targetActivity); } - boolean isSandboxActivityContext = sandboxActivitySdkBasedContext() - && r.intent.isSandboxActivity(mSystemContext); + boolean isSandboxActivityContext = + sandboxActivitySdkBasedContext() + && SdkSandboxActivityAuthority.isSdkSandboxActivity( + mSystemContext, r.intent); boolean isSandboxedSdkContextUsed = false; ContextImpl activityBaseContext; if (isSandboxActivityContext) { @@ -4041,11 +4043,12 @@ public final class ActivityThread extends ClientTransactionHandler */ @Nullable private ContextImpl createBaseContextForSandboxActivity(@NonNull ActivityClientRecord r) { - ActivityContextInfoProvider contextInfoProvider = ActivityContextInfoProvider.getInstance(); + SdkSandboxActivityAuthority sdkSandboxActivityAuthority = + SdkSandboxActivityAuthority.getInstance(); ActivityContextInfo contextInfo; try { - contextInfo = contextInfoProvider.getActivityContextInfo(r.intent); + contextInfo = sdkSandboxActivityAuthority.getActivityContextInfo(r.intent); } catch (IllegalArgumentException e) { Log.e(TAG, "Passed intent does not match an expected sandbox activity", e); return null; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b74b075c9f62..c5e132fc0c4b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -9927,10 +9927,11 @@ public class AppOpsManager { if (i != firstInteresting) { sb.append('\n'); } - if (!sFullLog && sb.length() + trace[i].toString().length() > 600) { + final String traceString = trace[i].toString(); + if (!sFullLog && sb.length() + traceString.length() > 600) { break; } - sb.append(trace[i]); + sb.append(traceString); } return sb.toString(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 08c18c8b7448..4f8e8dd813a1 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3482,7 +3482,8 @@ class ContextImpl extends Context { mResources = r; // only do this if the user already has more than one preferred locale - if (r.getConfiguration().getLocales().size() > 1) { + if (android.content.res.Flags.defaultLocale() + && r.getConfiguration().getLocales().size() > 1) { LocaleConfig lc = getUserId() < 0 ? LocaleConfig.fromContextIgnoringOverride(this) : new LocaleConfig(this); diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java index 06bff5df490a..48db18f4a1d7 100644 --- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -59,7 +59,7 @@ public abstract class ActivityLifecycleItem extends ActivityTransactionItem { } @Override - boolean isActivityLifecycleItem() { + public boolean isActivityLifecycleItem() { return true; } diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java index f94e22de06e5..a8d61db1ce3a 100644 --- a/core/java/android/app/servertransaction/ClientTransactionItem.java +++ b/core/java/android/app/servertransaction/ClientTransactionItem.java @@ -75,7 +75,7 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel /** * Whether this is a {@link ActivityLifecycleItem}. */ - boolean isActivityLifecycleItem() { + public boolean isActivityLifecycleItem() { return false; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 665ba1119550..c7a86fbe0171 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -12605,8 +12605,12 @@ public class Intent implements Parcelable, Cloneable { return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT; } - // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal} - /** @hide */ + /** + * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivity} instead. + * Once the other API is finalized this method will be removed. + * @hide + */ + @Deprecated @android.ravenwood.annotation.RavenwoodThrow public boolean isSandboxActivity(@NonNull Context context) { if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) { diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 63c11b779641..1cfdb8b37fcd 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,8 +16,11 @@ package android.content.pm; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -244,6 +247,14 @@ public class PackageInfo implements Parcelable { public Attribution[] attributions; /** + * The time at which the app was archived for the user. Units are as + * per {@link System#currentTimeMillis()}. + * @hide + */ + @CurrentTimeMillisLong + private long mArchiveTimeMillis; + + /** * Flag for {@link #requestedPermissionsFlags}: the requested permission * is required for the application to run; the user can not optionally * disable it. Currently all permissions are required. @@ -508,6 +519,24 @@ public class PackageInfo implements Parcelable { return overlayTarget != null && mOverlayIsStatic; } + /** + * Returns the time at which the app was archived for the user. Units are as + * per {@link System#currentTimeMillis()}. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public @CurrentTimeMillisLong long getArchiveTimeMillis() { + return mArchiveTimeMillis; + } + + /** + * @hide + */ + public void setArchiveTimeMillis(@CurrentTimeMillisLong long value) { + mArchiveTimeMillis = value; + } + @Override public String toString() { return "PackageInfo{" @@ -575,6 +604,7 @@ public class PackageInfo implements Parcelable { } dest.writeBoolean(isApex); dest.writeBoolean(isActiveApex); + dest.writeLong(mArchiveTimeMillis); dest.restoreAllowSquashing(prevAllowSquashing); } @@ -640,5 +670,6 @@ public class PackageInfo implements Parcelable { } isApex = source.readBoolean(); isActiveApex = source.readBoolean(); + mArchiveTimeMillis = source.readLong(); } } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 6681e54beaa1..e9a2aaad6579 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -363,6 +363,19 @@ public class PackageInstaller { "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; /** + * Extra field for the unarchive ID. Sent as + * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. + * + * @see Session#setUnarchiveId(int) + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final String EXTRA_UNARCHIVE_ID = + "android.content.pm.extra.UNARCHIVE_ID"; + + /** * If true, the requestor of the unarchival has specified that the app should be unarchived * for {@link android.os.UserHandle#ALL}. * @@ -2268,6 +2281,8 @@ public class PackageInstaller { * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * visible to the caller or if the package has no * installer on the device anymore to unarchive it. + * @throws IOException If parameters were unsatisfiable, such as lack of disk space. + * * @hide */ @RequiresPermission(anyOf = { @@ -2276,11 +2291,12 @@ public class PackageInstaller { @SystemApi @FlaggedApi(Flags.FLAG_ARCHIVING) public void requestUnarchive(@NonNull String packageName) - throws PackageManager.NameNotFoundException { + throws IOException, PackageManager.NameNotFoundException { try { mInstaller.requestUnarchive(packageName, mInstallerPackageName, new UserHandle(mUserId)); } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2548,6 +2564,8 @@ public class PackageInstaller { public boolean applicationEnabledSettingPersistent = false; /** {@hide} */ public int developmentInstallFlags = 0; + /** {@hide} */ + public int unarchiveId = -1; private final ArrayMap<String, Integer> mPermissionStates; @@ -2599,6 +2617,7 @@ public class PackageInstaller { packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); + unarchiveId = source.readInt(); } /** {@hide} */ @@ -2632,6 +2651,7 @@ public class PackageInstaller { ret.packageSource = packageSource; ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; + ret.unarchiveId = unarchiveId; return ret; } @@ -3270,6 +3290,22 @@ public class PackageInstaller { } } + /** + * Used to set the unarchive ID received as part of an + * {@link Intent#ACTION_UNARCHIVE_PACKAGE}. + * + * <p> The ID should be retrieved from the unarchive intent and passed into the + * session that's being created to unarchive the app in question. Used to link the unarchive + * intent and the install session to disambiguate. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + @SystemApi + public void setUnarchiveId(int unarchiveId) { + this.unarchiveId = unarchiveId; + } + /** @hide */ @NonNull public ArrayMap<String, Integer> getPermissionStates() { @@ -3327,6 +3363,7 @@ public class PackageInstaller { pw.printPair("applicationEnabledSettingPersistent", applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); + pw.printPair("unarchiveId", unarchiveId); pw.println(); } @@ -3370,6 +3407,7 @@ public class PackageInstaller { dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(developmentInstallFlags); + dest.writeInt(unarchiveId); } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index fe66759dc03f..c3b3423c1a57 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -43,6 +43,7 @@ import android.app.PackageInstallObserver; import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; import android.app.usage.StorageStatsManager; +import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -710,12 +711,31 @@ public abstract class PackageManager { */ @SystemApi public interface OnPermissionsChangedListener { - /** - * Called when the permissions for a UID change. + * Called when the permissions for a UID change for the default device. + * * @param uid The UID with a change. + * @see Context#DEVICE_ID_DEFAULT */ - public void onPermissionsChanged(int uid); + void onPermissionsChanged(int uid); + + /** + * Called when the permissions for a UID change for a device, including virtual devices. + * + * @param uid The UID of permission change event. + * @param persistentDeviceId The persistent device ID of permission change event. + * + * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId() + * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT + */ + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { + Objects.requireNonNull(persistentDeviceId); + if (Objects.equals(persistentDeviceId, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) { + onPermissionsChanged(uid); + } + } } /** @hide */ @@ -1481,6 +1501,7 @@ public abstract class PackageManager { INSTALL_STAGED, INSTALL_REQUEST_UPDATE_OWNERSHIP, INSTALL_IGNORE_DEXOPT_PROFILE, + INSTALL_UNARCHIVE_DRAFT, }) @Retention(RetentionPolicy.SOURCE) public @interface InstallFlags {} @@ -1725,6 +1746,16 @@ public abstract class PackageManager { public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28; /** + * If set, then the session is a draft session created for an upcoming unarchival by its + * installer. + * + * @see PackageInstaller#requestUnarchive(String) + * + * @hide + */ + public static final int INSTALL_UNARCHIVE_DRAFT = 1 << 29; + + /** * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is * a development-only feature and should not be used on end user devices. * @@ -6373,9 +6404,8 @@ public abstract class PackageManager { /** * Permission flags set when granting or revoking a permission. * - * @hide + * @removed mistakenly exposed as system-api previously */ - @SystemApi @IntDef(prefix = { "FLAG_PERMISSION_" }, value = { FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_FIXED, diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4791a8341912..f71e853a1170 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -721,8 +721,19 @@ public abstract class DisplayManagerInternal { public interface DisplayOffloadSession { /** Provide the display state to use in place of state DOZE. */ void setDozeStateOverride(int displayState); - /** Returns the associated DisplayOffloader. */ - DisplayOffloader getDisplayOffloader(); + + /** Whether the session is active. */ + boolean isActive(); + + /** + * Update the brightness from the offload chip. + * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and + * {@link PowerManager.BRIGHTNESS_MAX}, or + * {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} which removes + * the brightness from offload. Other values will be ignored. + */ + void updateBrightness(float brightness); + /** Returns whether displayoffload supports the given display state. */ static boolean isSupportedOffloadState(int displayState) { return Display.isSuspendedState(displayState); diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 440585c845f8..09741e52d67d 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -149,6 +149,7 @@ public final class HdmiControlManager { public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; + /** @removed mistakenly exposed previously */ @IntDef ({ RESULT_SUCCESS, RESULT_TIMEOUT, diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java index 7f2d8a026a2f..5985c39034ea 100644 --- a/core/java/android/hardware/input/VirtualDpad.java +++ b/core/java/android/hardware/input/VirtualDpad.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; import android.view.KeyEvent; import java.util.Arrays; @@ -80,7 +81,10 @@ public class VirtualDpad extends VirtualInputDevice { + event.getKeyCode() + " sent to a VirtualDpad input device."); } - mVirtualDevice.sendDpadKeyEvent(mToken, event); + if (!mVirtualDevice.sendDpadKeyEvent(mToken, event)) { + Log.w(TAG, "Failed to send key event to virtual dpad " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java index 931e1ff10505..affa4ed7e983 100644 --- a/core/java/android/hardware/input/VirtualInputDevice.java +++ b/core/java/android/hardware/input/VirtualInputDevice.java @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; import java.io.Closeable; @@ -32,6 +33,8 @@ import java.io.Closeable; */ abstract class VirtualInputDevice implements Closeable { + protected static final String TAG = "VirtualInputDevice"; + /** * The virtual device to which this VirtualInputDevice belongs to. */ @@ -67,6 +70,7 @@ abstract class VirtualInputDevice implements Closeable { @Override @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { + Log.d(TAG, "Closing virtual input device " + mConfig.getInputDeviceName()); try { mVirtualDevice.unregisterInputDevice(mToken); } catch (RemoteException e) { diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java index a8caa58ada01..a87980c34f2d 100644 --- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java +++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java @@ -19,6 +19,10 @@ package android.hardware.input; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; +import android.view.Display; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; /** * Common configurations to create virtual input devices. @@ -27,6 +31,15 @@ import android.os.Parcel; */ @SystemApi public abstract class VirtualInputDeviceConfig { + + /** + * The maximum length of a device name (in bytes in UTF-8 encoding). + * + * This limitation comes directly from uinput. + * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h + */ + private static final int DEVICE_NAME_MAX_LENGTH = 80; + /** The vendor id uniquely identifies the company who manufactured the device. */ private final int mVendorId; /** @@ -44,18 +57,33 @@ public abstract class VirtualInputDeviceConfig { mVendorId = builder.mVendorId; mProductId = builder.mProductId; mAssociatedDisplayId = builder.mAssociatedDisplayId; - mInputDeviceName = builder.mInputDeviceName; + mInputDeviceName = Objects.requireNonNull(builder.mInputDeviceName); + + if (mAssociatedDisplayId == Display.INVALID_DISPLAY) { + throw new IllegalArgumentException( + "Display association is required for virtual input devices."); + } + + // Comparison is greater or equal because the device name must fit into a const char* + // including the \0-terminator. Therefore the actual number of bytes that can be used + // for device name is DEVICE_NAME_MAX_LENGTH - 1 + if (mInputDeviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) { + throw new IllegalArgumentException("Input device name exceeds maximum length of " + + DEVICE_NAME_MAX_LENGTH + "bytes: " + mInputDeviceName); + } } protected VirtualInputDeviceConfig(@NonNull Parcel in) { mVendorId = in.readInt(); mProductId = in.readInt(); mAssociatedDisplayId = in.readInt(); - mInputDeviceName = in.readString8(); + mInputDeviceName = Objects.requireNonNull(in.readString8()); } /** * The vendor id uniquely identifies the company who manufactured the device. + * + * @see Builder#setVendorId(int) (int) */ public int getVendorId() { return mVendorId; @@ -64,6 +92,8 @@ public abstract class VirtualInputDeviceConfig { /** * The product id uniquely identifies which product within the address space of a given vendor, * identified by the device's vendor id. + * + * @see Builder#setProductId(int) */ public int getProductId() { return mProductId; @@ -71,6 +101,8 @@ public abstract class VirtualInputDeviceConfig { /** * The associated display ID of the virtual input device. + * + * @see Builder#setAssociatedDisplayId(int) */ public int getAssociatedDisplayId() { return mAssociatedDisplayId; @@ -78,6 +110,8 @@ public abstract class VirtualInputDeviceConfig { /** * The name of the virtual input device. + * + * @see Builder#setInputDeviceName(String) */ @NonNull public String getInputDeviceName() { @@ -117,11 +151,12 @@ public abstract class VirtualInputDeviceConfig { private int mVendorId; private int mProductId; - private int mAssociatedDisplayId; - @NonNull + private int mAssociatedDisplayId = Display.INVALID_DISPLAY; private String mInputDeviceName; - /** @see VirtualInputDeviceConfig#getVendorId(). */ + /** + * Sets the vendor id of the device, identifying the company who manufactured the device. + */ @NonNull public T setVendorId(int vendorId) { mVendorId = vendorId; @@ -129,24 +164,40 @@ public abstract class VirtualInputDeviceConfig { } - /** @see VirtualInputDeviceConfig#getProductId(). */ + /** + * Sets the product id of the device, uniquely identifying the device within the address + * space of a given vendor, identified by the device's vendor id. + */ @NonNull public T setProductId(int productId) { mProductId = productId; return self(); } - /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */ + /** + * Sets the associated display ID of the virtual input device. Required. + * + * <p>The input device is restricted to the display with the given ID and may not send + * events to any other display.</p> + */ @NonNull public T setAssociatedDisplayId(int displayId) { mAssociatedDisplayId = displayId; return self(); } - /** @see VirtualInputDeviceConfig#getInputDeviceName(). */ + /** + * Sets the name of the virtual input device. Required. + * + * <p>The name must be unique among all input devices that belong to the same virtual + * device.</p> + * + * <p>The maximum allowed length of the name is 80 bytes in UTF-8 encoding, enforced by + * {@code UINPUT_MAX_NAME_SIZE}.</p> + */ @NonNull public T setInputDeviceName(@NonNull String deviceName) { - mInputDeviceName = deviceName; + mInputDeviceName = Objects.requireNonNull(deviceName); return self(); } diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java index c90f8932a89d..6eb2ae38ed82 100644 --- a/core/java/android/hardware/input/VirtualKeyboard.java +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; import android.view.KeyEvent; /** @@ -57,7 +58,10 @@ public class VirtualKeyboard extends VirtualInputDevice { "Unsupported key code " + event.getKeyCode() + " sent to a VirtualKeyboard input device."); } - mVirtualDevice.sendKeyEvent(mToken, event); + if (!mVirtualDevice.sendKeyEvent(mToken, event)) { + Log.w(TAG, "Failed to send key event to virtual keyboard " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 51f3f69eb78e..fb0f70049273 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -23,6 +23,7 @@ import android.companion.virtual.IVirtualDevice; import android.graphics.PointF; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; import android.view.MotionEvent; /** @@ -52,7 +53,10 @@ public class VirtualMouse extends VirtualInputDevice { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) { try { - mVirtualDevice.sendButtonEvent(mToken, event); + if (!mVirtualDevice.sendButtonEvent(mToken, event)) { + Log.w(TAG, "Failed to send button event to virtual mouse " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -69,7 +73,10 @@ public class VirtualMouse extends VirtualInputDevice { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) { try { - mVirtualDevice.sendScrollEvent(mToken, event); + if (!mVirtualDevice.sendScrollEvent(mToken, event)) { + Log.w(TAG, "Failed to send scroll event to virtual mouse " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -85,7 +92,10 @@ public class VirtualMouse extends VirtualInputDevice { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) { try { - mVirtualDevice.sendRelativeEvent(mToken, event); + if (!mVirtualDevice.sendRelativeEvent(mToken, event)) { + Log.w(TAG, "Failed to send relative event to virtual mouse " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java index 61d72e2fd554..3dbb38568f68 100644 --- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java +++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; /** * A virtual navigation touchpad representing a touch-based input mechanism on a remote device. @@ -53,7 +54,10 @@ public class VirtualNavigationTouchpad extends VirtualInputDevice { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull VirtualTouchEvent event) { try { - mVirtualDevice.sendTouchEvent(mToken, event); + if (!mVirtualDevice.sendTouchEvent(mToken, event)) { + Log.w(TAG, "Failed to send touch event to virtual navigation touchpad " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java index 4ac439e0eff1..2c800aadef37 100644 --- a/core/java/android/hardware/input/VirtualTouchscreen.java +++ b/core/java/android/hardware/input/VirtualTouchscreen.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.util.Log; /** * A virtual touchscreen representing a touch-based display input mechanism on a remote device. @@ -47,7 +48,10 @@ public class VirtualTouchscreen extends VirtualInputDevice { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull VirtualTouchEvent event) { try { - mVirtualDevice.sendTouchEvent(mToken, event); + if (!mVirtualDevice.sendTouchEvent(mToken, event)) { + Log.w(TAG, "Failed to send touch event to virtual touchscreen " + + mConfig.getInputDeviceName()); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index c7ec052b309b..7e5c141a399a 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -109,7 +109,10 @@ public final class ProgramSelector implements Parcelable { /** @deprecated use {@link ProgramIdentifier} instead */ @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; - /** @deprecated use {@link ProgramIdentifier} instead */ + /** + * @deprecated use {@link ProgramIdentifier} instead + * @removed mistakenly exposed previously + */ @Deprecated @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { PROGRAM_TYPE_INVALID, @@ -397,6 +400,7 @@ public final class ProgramSelector implements Parcelable { */ @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END; + /** @removed mistakenly exposed previously */ @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { IDENTIFIER_TYPE_INVALID, IDENTIFIER_TYPE_AMFM_FREQUENCY, diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 237ec0129ed9..f0f7e8a22e2a 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -123,6 +123,7 @@ public class RadioManager { /** AM HD radio or DRM band. * @see BandDescriptor */ public static final int BAND_AM_HD = 3; + /** @removed mistakenly exposed previously */ @IntDef(prefix = { "BAND_" }, value = { BAND_INVALID, BAND_AM, diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index f16e2439f3f4..e2d215ebfed4 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -33,6 +33,8 @@ import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; import android.window.WindowProviderService; +import com.android.internal.annotations.VisibleForTesting; + import java.io.FileDescriptor; import java.io.PrintWriter; @@ -72,8 +74,9 @@ public abstract class AbstractInputMethodService extends WindowProviderService * {@code null} if {@link #onCreateInputMethodInterface()} is not yet called. * @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Nullable - protected final InputMethod getInputMethodInternal() { + public final InputMethod getInputMethodInternal() { return mInputMethod; } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ba80811e198c..18d3e5e02fbe 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -149,6 +149,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethod; @@ -3997,6 +3998,16 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Returns whether the IME navigation bar is currently shown, for testing purposes. + * + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public final boolean isImeNavigationBarShownForTesting() { + return mNavigationBarController.isShown(); + } + + /** * Used to inject custom {@link InputMethodServiceInternal}. * * @return the {@link InputMethodServiceInternal} to be used. diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 8be4c5858694..9c55b0ee0623 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -77,6 +77,10 @@ final class NavigationBarController { default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) { } + default boolean isShown() { + return false; + } + default String toDebugString() { return "No-op implementation"; } @@ -117,6 +121,13 @@ final class NavigationBarController { mImpl.onNavButtonFlagsChanged(navButtonFlags); } + /** + * Returns whether the IME navigation bar is currently shown. + */ + boolean isShown() { + return mImpl.isShown(); + } + String toDebugString() { return mImpl.toDebugString(); } @@ -561,6 +572,12 @@ final class NavigationBarController { } @Override + public boolean isShown() { + return mNavigationBarFrame != null + && mNavigationBarFrame.getVisibility() == View.VISIBLE; + } + + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar + " mNavigationBarFrame=" + mNavigationBarFrame diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index 19ba6a1d22ed..dbb312720373 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -23,7 +23,6 @@ import android.content.Context; import android.os.IBinder; import android.os.ServiceManager; -import com.android.net.flags.Flags; import com.android.net.module.util.PermissionUtils; /** * Constants and utilities for client code communicating with the network stack service. @@ -104,16 +103,4 @@ public class NetworkStack { final @NonNull String... otherPermissions) { PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions); } - - /** - * Get setting of the "set_data_saver_via_cm" flag. - * - * @hide - */ - // A workaround for aconfig. Currently, aconfig value read from platform and mainline code can - // be inconsistent. To avoid the problem, CTS for mainline code can get the flag value by this - // method. - public static boolean getDataSaverViaCmFlag() { - return Flags.setDataSaverViaCm(); - } } diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 597c948bd515..e331c95288d9 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -21,6 +21,7 @@ package android.nfc.cardemulation; import android.annotation.FlaggedApi; +import android.compat.annotation.UnsupportedAppUsage; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -134,8 +135,9 @@ public final class ApduServiceInfo implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, - List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups, + ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost) { this(info, onHost, description, staticAidGroups, dynamicAidGroups, @@ -147,7 +149,7 @@ public final class ApduServiceInfo implements Parcelable { * @hide */ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, - List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups, + ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) { diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index a5f8844a2921..cd52b5c0f7f3 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -115,6 +115,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package"; static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground"; static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background"; + static final String XML_ATTR_TIME_IN_FOREGROUND_SERVICE = "time_in_foreground_service"; // We need about 700 bytes per UID private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700; diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl index fe85da26e610..6b43e73d10e7 100644 --- a/core/java/android/os/IHintSession.aidl +++ b/core/java/android/os/IHintSession.aidl @@ -17,8 +17,6 @@ package android.os; -import android.os.WorkDuration; - /** {@hide} */ oneway interface IHintSession { void updateTargetWorkDuration(long targetDurationNanos); @@ -26,5 +24,4 @@ oneway interface IHintSession { void close(); void sendHint(int hint); void setMode(int mode, boolean enabled); - void reportActualWorkDuration2(in WorkDuration[] workDurations); } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 8f7725ecaba0..655debc84d1d 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -87,3 +87,7 @@ per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com # PerformanceHintManager per-file PerformanceHintManager.java = file:/ADPF_OWNERS + +# IThermal interfaces +per-file IThermal* = file:/THERMAL_OWNERS + diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index e0059105c21f..11084b88fad1 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -103,7 +103,7 @@ public final class PerformanceHintManager { * Any call in this class will change its internal data, so you must do your own thread * safety to protect from racing. * - * All timings should be in {@link SystemClock#uptimeNanos()}. + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. */ public static class Session implements Closeable { private long mNativeSessionPtr; @@ -269,40 +269,6 @@ public final class PerformanceHintManager { public @Nullable int[] getThreadIds() { return nativeGetThreadIds(mNativeSessionPtr); } - - /** - * Reports the work duration for the last cycle of work. - * - * The system will attempt to adjust the core placement of the threads within the thread - * group and/or the frequency of the core on which they are run to bring the actual duration - * close to the target duration. - * - * @param workDuration the work duration of each component. - * @throws IllegalArgumentException if work period start timestamp is not positive, or - * actual total duration is not positive, or actual CPU duration is not positive, - * or actual GPU duration is negative. - */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) - public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { - if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - if (workDuration.mActualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - if (workDuration.mActualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - if (workDuration.mActualGpuDurationNanos < 0) { - throw new IllegalArgumentException( - "the actual GPU duration should be non negative."); - } - nativeReportActualWorkDuration(mNativeSessionPtr, - workDuration.mWorkPeriodStartTimestampNanos, - workDuration.mActualTotalDurationNanos, - workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos); - } } private static native long nativeAcquireManager(); @@ -319,7 +285,4 @@ public final class PerformanceHintManager { private static native void nativeSetThreads(long nativeSessionPtr, int[] tids); private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled); - private static native void nativeReportActualWorkDuration(long nativeSessionPtr, - long workPeriodStartTimestampNanos, long actualTotalDurationNanos, - long actualCpuDurationNanos, long actualGpuDurationNanos); } diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index e2a58338230c..49a0bd3289aa 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -16,7 +16,6 @@ package android.os; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.IAlarmManager; import android.app.time.UnixEpochTime; @@ -203,8 +202,8 @@ public final class SystemClock { * Returns nanoseconds since boot, not counting time spent in deep sleep. * * @return nanoseconds of non-sleep uptime since boot. + * @hide */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) @CriticalNative @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 03a1b6f7fe01..3eea94eaf2e6 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -71,7 +71,8 @@ public final class UidBatteryConsumer extends BatteryConsumer { static final int COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN = COLUMN_INDEX_UID + 1; static final int COLUMN_INDEX_TIME_IN_FOREGROUND = COLUMN_INDEX_UID + 2; static final int COLUMN_INDEX_TIME_IN_BACKGROUND = COLUMN_INDEX_UID + 3; - static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 4; + static final int COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE = COLUMN_INDEX_UID + 4; + static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 5; UidBatteryConsumer(BatteryConsumerData data) { super(data); @@ -92,17 +93,35 @@ public final class UidBatteryConsumer extends BatteryConsumer { /** * Returns the amount of time in milliseconds this UID spent in the specified state. + * @deprecated use {@link #getTimeInProcessStateMs} instead. */ + @Deprecated public long getTimeInStateMs(@State int state) { switch (state) { case STATE_BACKGROUND: - return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND); + return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND) + + mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE); case STATE_FOREGROUND: return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND); } return 0; } + /** + * Returns the amount of time in milliseconds this UID spent in the specified process state. + */ + public long getTimeInProcessStateMs(@ProcessState int state) { + switch (state) { + case PROCESS_STATE_BACKGROUND: + return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND); + case PROCESS_STATE_FOREGROUND: + return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND); + case PROCESS_STATE_FOREGROUND_SERVICE: + return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE); + } + return 0; + } + @Override public void dump(PrintWriter pw, boolean skipEmptyComponents) { pw.print("UID "); @@ -158,9 +177,11 @@ public final class UidBatteryConsumer extends BatteryConsumer { packageWithHighestDrain); } serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND, - getTimeInStateMs(STATE_FOREGROUND)); + getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)); serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND, - getTimeInStateMs(STATE_BACKGROUND)); + getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)); + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE, + getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)); mPowerComponents.writeToXml(serializer); serializer.endTag(null, BatteryUsageStats.XML_TAG_UID); } @@ -180,10 +201,13 @@ public final class UidBatteryConsumer extends BatteryConsumer { consumerBuilder.setPackageWithHighestDrain( parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE)); - consumerBuilder.setTimeInStateMs(STATE_FOREGROUND, + consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND)); - consumerBuilder.setTimeInStateMs(STATE_BACKGROUND, + consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND)); + consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, + parser.getAttributeLong(null, + BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE)); while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) && eventType != XmlPullParser.END_DOCUMENT) { @@ -255,7 +279,9 @@ public final class UidBatteryConsumer extends BatteryConsumer { /** * Sets the duration, in milliseconds, that this UID was active in a particular state, * such as foreground or background. + * @deprecated use {@link #setTimeInProcessStateMs} instead. */ + @Deprecated @NonNull public Builder setTimeInStateMs(@State int state, long timeInStateMs) { switch (state) { @@ -272,6 +298,28 @@ public final class UidBatteryConsumer extends BatteryConsumer { } /** + * Sets the duration, in milliseconds, that this UID was active in a particular process + * state, such as foreground service. + */ + @NonNull + public Builder setTimeInProcessStateMs(@ProcessState int state, long timeInProcessStateMs) { + switch (state) { + case PROCESS_STATE_FOREGROUND: + mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND, timeInProcessStateMs); + break; + case PROCESS_STATE_BACKGROUND: + mData.putLong(COLUMN_INDEX_TIME_IN_BACKGROUND, timeInProcessStateMs); + break; + case PROCESS_STATE_FOREGROUND_SERVICE: + mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE, timeInProcessStateMs); + break; + default: + throw new IllegalArgumentException("Unsupported process state: " + state); + } + return this; + } + + /** * Marks the UidBatteryConsumer for exclusion from the result set. */ public Builder excludeFromBatteryUsageStats() { @@ -285,12 +333,15 @@ public final class UidBatteryConsumer extends BatteryConsumer { public Builder add(UidBatteryConsumer consumer) { mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents); - setTimeInStateMs(STATE_FOREGROUND, + setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND) - + consumer.getTimeInStateMs(STATE_FOREGROUND)); - setTimeInStateMs(STATE_BACKGROUND, + + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)); + setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, mData.getLong(COLUMN_INDEX_TIME_IN_BACKGROUND) - + consumer.getTimeInStateMs(STATE_BACKGROUND)); + + consumer.getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)); + setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, + mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE) + + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)); if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { mPackageWithHighestDrain = consumer.getPackageWithHighestDrain(); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 2419a4c391f2..08d6e028f08c 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -248,7 +248,7 @@ public class UserManager { @SystemApi public static final int RESTRICTION_SOURCE_PROFILE_OWNER = 0x4; - /** @hide */ + /** @removed mistakenly exposed as system-api previously */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "RESTRICTION_" }, value = { RESTRICTION_NOT_SET, @@ -256,7 +256,6 @@ public class UserManager { RESTRICTION_SOURCE_DEVICE_OWNER, RESTRICTION_SOURCE_PROFILE_OWNER }) - @SystemApi public @interface UserRestrictionSource {} /** diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java deleted file mode 100644 index 4fdc34fb60d1..000000000000 --- a/core/java/android/os/WorkDuration.java +++ /dev/null @@ -1,213 +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.os; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; - -import java.util.Objects; - -/** - * WorkDuration contains the measured time in nano seconds of the workload - * in each component, see - * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ -@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) -public final class WorkDuration implements Parcelable { - long mWorkPeriodStartTimestampNanos = 0; - long mActualTotalDurationNanos = 0; - long mActualCpuDurationNanos = 0; - long mActualGpuDurationNanos = 0; - long mTimestampNanos = 0; - - public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() { - @Override - public WorkDuration createFromParcel(Parcel in) { - return new WorkDuration(in); - } - - @Override - public WorkDuration[] newArray(int size) { - return new WorkDuration[size]; - } - }; - - public WorkDuration() {} - - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * @hide - */ - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos, - long timestampNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - mTimestampNanos = timestampNanos; - } - - WorkDuration(@NonNull Parcel in) { - mWorkPeriodStartTimestampNanos = in.readLong(); - mActualTotalDurationNanos = in.readLong(); - mActualCpuDurationNanos = in.readLong(); - mActualGpuDurationNanos = in.readLong(); - mTimestampNanos = in.readLong(); - } - - /** - * Sets the work period start timestamp in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) { - if (workPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - } - - /** - * Sets the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualTotalDurationNanos(long actualTotalDurationNanos) { - if (actualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - mActualTotalDurationNanos = actualTotalDurationNanos; - } - - /** - * Sets the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualCpuDurationNanos(long actualCpuDurationNanos) { - if (actualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - mActualCpuDurationNanos = actualCpuDurationNanos; - } - - /** - * Sets the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualGpuDurationNanos(long actualGpuDurationNanos) { - if (actualGpuDurationNanos < 0) { - throw new IllegalArgumentException("the actual GPU duration should be non negative."); - } - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * Returns the work period start timestamp based in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getWorkPeriodStartTimestampNanos() { - return mWorkPeriodStartTimestampNanos; - } - - /** - * Returns the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualTotalDurationNanos() { - return mActualTotalDurationNanos; - } - - /** - * Returns the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualCpuDurationNanos() { - return mActualCpuDurationNanos; - } - - /** - * Returns the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualGpuDurationNanos() { - return mActualGpuDurationNanos; - } - - /** - * @hide - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeLong(mWorkPeriodStartTimestampNanos); - dest.writeLong(mActualTotalDurationNanos); - dest.writeLong(mActualCpuDurationNanos); - dest.writeLong(mActualGpuDurationNanos); - dest.writeLong(mTimestampNanos); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof WorkDuration)) { - return false; - } - WorkDuration workDuration = (WorkDuration) obj; - return workDuration.mTimestampNanos == this.mTimestampNanos - && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos - && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos - && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos - && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos; - } - - @Override - public int hashCode() { - return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos, - mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos); - } -} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index d405d1d0cec6..a78f221fc962 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -61,11 +61,4 @@ flag { namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." bug: "305067031" -} - -flag { - name: "adpf_gpu_report_actual_work_duration" - namespace: "game" - description: "Guards the ADPF GPU APIs." - bug: "284324521" -} +}
\ No newline at end of file diff --git a/core/java/android/permission/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl index cc52a7210737..afacf1a74ca6 100644 --- a/core/java/android/permission/IOnPermissionsChangeListener.aidl +++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl @@ -21,5 +21,5 @@ package android.permission; * {@hide} */ oneway interface IOnPermissionsChangeListener { - void onPermissionsChanged(int uid); + void onPermissionsChanged(int uid, String deviceId); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index e10ea10e2a29..7a158c548a38 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1725,7 +1725,7 @@ public final class PermissionManager { } private final class OnPermissionsChangeListenerDelegate - extends IOnPermissionsChangeListener.Stub implements Handler.Callback{ + extends IOnPermissionsChangeListener.Stub implements Handler.Callback { private static final int MSG_PERMISSIONS_CHANGED = 1; private final PackageManager.OnPermissionsChangedListener mListener; @@ -1738,8 +1738,8 @@ public final class PermissionManager { } @Override - public void onPermissionsChanged(int uid) { - mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); + public void onPermissionsChanged(int uid, String deviceId) { + mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget(); } @Override @@ -1747,7 +1747,8 @@ public final class PermissionManager { switch (msg.what) { case MSG_PERMISSIONS_CHANGED: { final int uid = msg.arg1; - mListener.onPermissionsChanged(uid); + final String deviceId = msg.obj.toString(); + mListener.onPermissionsChanged(uid, deviceId); return true; } default: diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 9bdd0c2db779..0133bd822182 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -36,10 +36,3 @@ flag { description: "Collect sepolicy hash from sysfs" bug: "308471499" } - -flag { - name: "extend_ecm_to_all_settings" - namespace: "responsible_apis" - description: "Allow all app settings to be restrictable via configuration" - bug: "297372999" -} diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig new file mode 100644 index 000000000000..4e5588cce1c9 --- /dev/null +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -0,0 +1,22 @@ +package: "android.security" + +flag { + name: "extend_ecm_to_all_settings" + namespace: "responsible_apis" + description: "Allow all app settings to be restrictable via configuration" + bug: "297372999" +} + +flag { + name: "asm_restrictions_enabled" + namespace: "responsible_apis" + description: "Enables ASM restrictions for activity starts and finishes" + bug: "230590090" +} + +flag { + name: "asm_toasts_enabled" + namespace: "responsible_apis" + description: "Enables toasts when ASM restrictions are triggered" + bug: "230590090" +} diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java index 5b096c641f78..db0b7ffa0913 100644 --- a/core/java/android/service/notification/ZenDeviceEffects.java +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -178,6 +178,16 @@ public final class ZenDeviceEffects implements Parcelable { return mMaximizeDoze; } + /** + * Whether any of the effects are set up. + * @hide + */ + public boolean hasEffects() { + return mGrayscale || mSuppressAmbientDisplay || mDimWallpaper || mNightMode + || mDisableAutoBrightness || mDisableTapToWake || mDisableTiltToWake + || mDisableTouch || mMinimizeRadioUsage || mMaximizeDoze; + } + /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */ @NonNull public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() { diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index fedad895961d..f1d35b5b1185 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -25,6 +25,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import android.annotation.FlaggedApi; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; @@ -185,6 +186,18 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; + private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; + private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = + "zdeSuppressAmbientDisplay"; + private static final String DEVICE_EFFECT_DIM_WALLPAPER = "zdeDimWallpaper"; + private static final String DEVICE_EFFECT_USE_NIGHT_MODE = "zdeUseNightMode"; + private static final String DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS = "zdeDisableAutoBrightness"; + private static final String DEVICE_EFFECT_DISABLE_TAP_TO_WAKE = "zdeDisableTapToWake"; + private static final String DEVICE_EFFECT_DISABLE_TILT_TO_WAKE = "zdeDisableTiltToWake"; + private static final String DEVICE_EFFECT_DISABLE_TOUCH = "zdeDisableTouch"; + private static final String DEVICE_EFFECT_MINIMIZE_RADIO_USAGE = "zdeMinimizeRadioUsage"; + private static final String DEVICE_EFFECT_MAXIMIZE_DOZE = "zdeMaximizeDoze"; + @UnsupportedAppUsage public boolean allowAlarms = DEFAULT_ALLOW_ALARMS; public boolean allowMedia = DEFAULT_ALLOW_MEDIA; @@ -630,6 +643,7 @@ public class ZenModeConfig implements Parcelable { rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false); rt.zenPolicy = readZenPolicyXml(parser); if (Flags.modesApi()) { + rt.zenDeviceEffects = readZenDeviceEffectsXml(parser); rt.allowManualInvocation = safeBoolean(parser, RULE_ATT_ALLOW_MANUAL, false); rt.iconResId = safeInt(parser, RULE_ATT_ICON, 0); rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); @@ -667,6 +681,9 @@ public class ZenModeConfig implements Parcelable { if (rule.zenPolicy != null) { writeZenPolicyXml(rule.zenPolicy, out); } + if (Flags.modesApi() && rule.zenDeviceEffects != null) { + writeZenDeviceEffectsXml(rule.zenDeviceEffects, out); + } out.attributeBoolean(null, RULE_ATT_MODIFIED, rule.modified); if (Flags.modesApi()) { out.attributeBoolean(null, RULE_ATT_ALLOW_MANUAL, rule.allowManualInvocation); @@ -859,6 +876,57 @@ public class ZenModeConfig implements Parcelable { } } + @Nullable + private static ZenDeviceEffects readZenDeviceEffectsXml(TypedXmlPullParser parser) { + ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale( + safeBoolean(parser, DEVICE_EFFECT_DISPLAY_GRAYSCALE, false)) + .setShouldSuppressAmbientDisplay( + safeBoolean(parser, DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY, false)) + .setShouldDimWallpaper(safeBoolean(parser, DEVICE_EFFECT_DIM_WALLPAPER, false)) + .setShouldUseNightMode(safeBoolean(parser, DEVICE_EFFECT_USE_NIGHT_MODE, false)) + .setShouldDisableAutoBrightness( + safeBoolean(parser, DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS, false)) + .setShouldDisableTapToWake( + safeBoolean(parser, DEVICE_EFFECT_DISABLE_TAP_TO_WAKE, false)) + .setShouldDisableTiltToWake( + safeBoolean(parser, DEVICE_EFFECT_DISABLE_TILT_TO_WAKE, false)) + .setShouldDisableTouch(safeBoolean(parser, DEVICE_EFFECT_DISABLE_TOUCH, false)) + .setShouldMinimizeRadioUsage( + safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false)) + .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false)) + .build(); + + return deviceEffects.hasEffects() ? deviceEffects : null; + } + + private static void writeZenDeviceEffectsXml(ZenDeviceEffects deviceEffects, + TypedXmlSerializer out) throws IOException { + writeBooleanIfTrue(out, DEVICE_EFFECT_DISPLAY_GRAYSCALE, + deviceEffects.shouldDisplayGrayscale()); + writeBooleanIfTrue(out, DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY, + deviceEffects.shouldSuppressAmbientDisplay()); + writeBooleanIfTrue(out, DEVICE_EFFECT_DIM_WALLPAPER, deviceEffects.shouldDimWallpaper()); + writeBooleanIfTrue(out, DEVICE_EFFECT_USE_NIGHT_MODE, deviceEffects.shouldUseNightMode()); + writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_AUTO_BRIGHTNESS, + deviceEffects.shouldDisableAutoBrightness()); + writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TAP_TO_WAKE, + deviceEffects.shouldDisableTapToWake()); + writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TILT_TO_WAKE, + deviceEffects.shouldDisableTiltToWake()); + writeBooleanIfTrue(out, DEVICE_EFFECT_DISABLE_TOUCH, deviceEffects.shouldDisableTouch()); + writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, + deviceEffects.shouldMinimizeRadioUsage()); + writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze()); + } + + private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value) + throws IOException { + if (value) { + out.attributeBoolean(null, att, true); + } + } + public static boolean isValidHour(int val) { return val >= 0 && val < 24; } @@ -1755,6 +1823,8 @@ public class ZenModeConfig implements Parcelable { // package name, only used for manual rules when they have turned DND on. public String enabler; public ZenPolicy zenPolicy; + @FlaggedApi(Flags.FLAG_MODES_API) + @Nullable public ZenDeviceEffects zenDeviceEffects; public boolean modified; // rule has been modified from initial creation public String pkg; public int type = AutomaticZenRule.TYPE_UNKNOWN; @@ -1784,6 +1854,9 @@ public class ZenModeConfig implements Parcelable { enabler = source.readString(); } zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); + if (Flags.modesApi()) { + zenDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); + } modified = source.readInt() == 1; pkg = source.readString(); if (Flags.modesApi()) { @@ -1828,6 +1901,9 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(0); } dest.writeParcelable(zenPolicy, 0); + if (Flags.modesApi()) { + dest.writeParcelable(zenDeviceEffects, 0); + } dest.writeInt(modified ? 1 : 0); dest.writeString(pkg); if (Flags.modesApi()) { @@ -1859,7 +1935,8 @@ public class ZenModeConfig implements Parcelable { .append(",condition=").append(condition); if (Flags.modesApi()) { - sb.append(",allowManualInvocation=").append(allowManualInvocation) + sb.append(",deviceEffects=").append(zenDeviceEffects) + .append(",allowManualInvocation=").append(allowManualInvocation) .append(",iconResId=").append(iconResId) .append(",triggerDescription=").append(triggerDescription) .append(",type=").append(type); @@ -1917,6 +1994,7 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesApi()) { return finalEquals + && Objects.equals(other.zenDeviceEffects, zenDeviceEffects) && other.allowManualInvocation == allowManualInvocation && other.iconResId == iconResId && Objects.equals(other.triggerDescription, triggerDescription) @@ -1930,8 +2008,9 @@ public class ZenModeConfig implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, - component, configurationActivity, pkg, id, enabler, zenPolicy, modified, - allowManualInvocation, iconResId, triggerDescription, type); + component, configurationActivity, pkg, id, enabler, zenPolicy, + zenDeviceEffects, modified, allowManualInvocation, iconResId, + triggerDescription, type); } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index eb55e40f2c7b..f345d7cb88fd 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -452,11 +452,12 @@ public class ZenModeDiff { public static final String FIELD_CREATION_TIME = "creationTime"; public static final String FIELD_ENABLER = "enabler"; public static final String FIELD_ZEN_POLICY = "zenPolicy"; + public static final String FIELD_ZEN_DEVICE_EFFECTS = "zenDeviceEffects"; public static final String FIELD_MODIFIED = "modified"; public static final String FIELD_PKG = "pkg"; public static final String FIELD_ALLOW_MANUAL = "allowManualInvocation"; public static final String FIELD_ICON_RES = "iconResId"; - public static final String FIELD_TRIGGER = "triggerDescription"; + public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription"; public static final String FIELD_TYPE = "type"; // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule @@ -534,19 +535,25 @@ public class ZenModeDiff { if (!Objects.equals(from.pkg, to.pkg)) { addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg)); } - if (!Objects.equals(from.triggerDescription, to.triggerDescription)) { - addField(FIELD_TRIGGER, - new FieldDiff<>(from.triggerDescription, to.triggerDescription)); - } - if (from.type != to.type) { - addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type)); - } - if (from.allowManualInvocation != to.allowManualInvocation) { - addField(FIELD_ALLOW_MANUAL, - new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation)); - } - if (!Objects.equals(from.iconResId, to.iconResId)) { - addField(FIELD_ICON_RES, new FieldDiff(from.iconResId, to.iconResId)); + if (android.app.Flags.modesApi()) { + if (!Objects.equals(from.zenDeviceEffects, to.zenDeviceEffects)) { + addField(FIELD_ZEN_DEVICE_EFFECTS, + new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects)); + } + if (!Objects.equals(from.triggerDescription, to.triggerDescription)) { + addField(FIELD_TRIGGER_DESCRIPTION, + new FieldDiff<>(from.triggerDescription, to.triggerDescription)); + } + if (from.type != to.type) { + addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type)); + } + if (from.allowManualInvocation != to.allowManualInvocation) { + addField(FIELD_ALLOW_MANUAL, + new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation)); + } + if (!Objects.equals(from.iconResId, to.iconResId)) { + addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResId, to.iconResId)); + } } } diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 9167153a0ef5..6da3206708a7 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -66,6 +66,7 @@ public class PersistentDataBlockManager { */ public static final int FLASH_LOCK_LOCKED = 1; + /** @removed mistakenly exposed previously */ @IntDef(prefix = { "FLASH_LOCK_" }, value = { FLASH_LOCK_UNKNOWN, FLASH_LOCK_LOCKED, diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 77e616b358cb..2105420b84cd 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -118,6 +118,7 @@ public class StaticLayout extends Layout { b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; b.mLineBreakConfig = LineBreakConfig.NONE; + b.mMinimumFontMetrics = null; return b; } @@ -130,6 +131,7 @@ public class StaticLayout extends Layout { b.mText = null; b.mLeftIndents = null; b.mRightIndents = null; + b.mMinimumFontMetrics = null; sPool.release(b); } @@ -139,6 +141,7 @@ public class StaticLayout extends Layout { mPaint = null; mLeftIndents = null; mRightIndents = null; + mMinimumFontMetrics = null; } public Builder setText(CharSequence source) { diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java index cc33af32ba93..10905e1b1908 100644 --- a/core/java/android/util/DataUnit.java +++ b/core/java/android/util/DataUnit.java @@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public enum DataUnit { KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } }, MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } }, diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 4654dbfa9531..d2c5975ea356 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -48,6 +48,9 @@ import java.util.regex.Pattern; * They carry a payload of one or more int, long, or String values. The * event-log-tags file defines the payload contents for each type code. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution.EventLog_host") public class EventLog { /** @hide */ public EventLog() {} @@ -416,6 +419,7 @@ public class EventLog { /** * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done. */ + @android.ravenwood.annotation.RavenwoodReplace private static synchronized void readTagsFile() { if (sTagCodes != null && sTagNames != null) return; @@ -441,8 +445,7 @@ public class EventLog { try { int num = Integer.parseInt(m.group(1)); String name = m.group(2); - sTagCodes.put(name, num); - sTagNames.put(num, name); + registerTagLocked(num, name); } catch (NumberFormatException e) { Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e); } @@ -454,4 +457,20 @@ public class EventLog { try { if (reader != null) reader.close(); } catch (IOException e) {} } } + + private static void registerTagLocked(int num, String name) { + sTagCodes.put(name, num); + sTagNames.put(num, name); + } + + private static synchronized void readTagsFile$ravenwood() { + // TODO: restore parsing logic once we carry into runtime + sTagCodes = new HashMap<String, Integer>(); + sTagNames = new HashMap<Integer, String>(); + + // Hard-code a few common tags + registerTagLocked(524288, "sysui_action"); + registerTagLocked(524290, "sysui_count"); + registerTagLocked(524291, "sysui_histogram"); + } } diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index c04a71c4d31b..413ae1f5cbcf 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -26,6 +26,7 @@ import java.util.Arrays; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class IntArray implements Cloneable { private static final int MIN_CAPACITY_INCREMENT = 12; diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java index 3101c0da6986..4c7ef08ddfcb 100644 --- a/core/java/android/util/LongArray.java +++ b/core/java/android/util/LongArray.java @@ -30,6 +30,7 @@ import java.util.Arrays; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LongArray implements Cloneable { private static final int MIN_CAPACITY_INCREMENT = 12; diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java index 3aeeccaba250..c0ceb9ea8204 100644 --- a/core/java/android/util/Slog.java +++ b/core/java/android/util/Slog.java @@ -31,6 +31,7 @@ import android.os.Build; * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Slog { private Slog() { @@ -216,6 +217,7 @@ public final class Slog { * @see Log#wtf(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false, true); } @@ -223,6 +225,7 @@ public final class Slog { /** * Similar to {@link #wtf(String, String)}, but does not output anything to the log. */ + @android.ravenwood.annotation.RavenwoodThrow public static void wtfQuiet(@Nullable String tag, @NonNull String msg) { Log.wtfQuiet(Log.LOG_ID_SYSTEM, tag, msg, true); } @@ -241,6 +244,7 @@ public final class Slog { * @see Log#wtfStack(String, String) */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodThrow public static int wtfStack(@Nullable String tag, @NonNull String msg) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true, true); } @@ -259,6 +263,7 @@ public final class Slog { * * @see Log#wtf(String, Throwable) */ + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false, true); } @@ -279,6 +284,7 @@ public final class Slog { * @see Log#wtf(String, String, Throwable) */ @UnsupportedAppUsage + @android.ravenwood.annotation.RavenwoodThrow public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false, true); } diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index d06b0ce1a2d8..bff8db13ad14 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -41,6 +41,8 @@ import java.util.List; /** * A class containing utility methods related to time zones. */ +@android.ravenwood.annotation.RavenwoodKeepPartialClass +@android.ravenwood.annotation.RavenwoodKeepStaticInitializer public class TimeUtils { /** @hide */ public TimeUtils() {} /** {@hide} */ @@ -180,6 +182,7 @@ public class TimeUtils { private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; + @android.ravenwood.annotation.RavenwoodKeep static private int accumField(int amt, int suffix, boolean always, int zeropad) { if (amt > 999) { int num = 0; @@ -202,6 +205,7 @@ public class TimeUtils { return 0; } + @android.ravenwood.annotation.RavenwoodKeep static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad) { if (always || amt > 0) { @@ -242,6 +246,7 @@ public class TimeUtils { return pos; } + @android.ravenwood.annotation.RavenwoodKeep private static int formatDurationLocked(long duration, int fieldLen) { if (sFormatStr.length < fieldLen) { sFormatStr = new char[fieldLen]; @@ -314,6 +319,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, StringBuilder builder) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, 0); @@ -322,6 +328,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, StringBuilder builder, int fieldLen) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, fieldLen); @@ -331,6 +338,7 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, fieldLen); @@ -340,6 +348,7 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @TestApi + @android.ravenwood.annotation.RavenwoodKeep public static String formatDuration(long duration) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, 0); @@ -349,11 +358,13 @@ public class TimeUtils { /** @hide Just for debugging; not internationalized. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long duration, PrintWriter pw) { formatDuration(duration, pw, 0); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long time, long now, StringBuilder sb) { if (time == 0) { sb.append("--"); @@ -363,6 +374,7 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static void formatDuration(long time, long now, PrintWriter pw) { if (time == 0) { pw.print("--"); @@ -372,16 +384,19 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatUptime(long time) { return formatTime(time, SystemClock.uptimeMillis()); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatRealtime(long time) { return formatTime(time, SystemClock.elapsedRealtime()); } /** @hide Just for debugging; not internationalized. */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatTime(long time, long referenceTime) { long diff = time - referenceTime; if (diff > 0) { @@ -402,6 +417,7 @@ public class TimeUtils { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @android.ravenwood.annotation.RavenwoodKeep public static String logTimeOfDay(long millis) { Calendar c = Calendar.getInstance(); if (millis >= 0) { @@ -413,6 +429,7 @@ public class TimeUtils { } /** {@hide} */ + @android.ravenwood.annotation.RavenwoodKeep public static String formatForLogging(long millis) { if (millis <= 0) { return "unknown"; @@ -426,6 +443,7 @@ public class TimeUtils { * * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static void dumpTime(PrintWriter pw, long time) { pw.print(sDumpDateFormat.format(new Date(time))); } @@ -457,6 +475,7 @@ public class TimeUtils { * * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) { pw.print(sDumpDateFormat.format(new Date(time))); if (time == now) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 1acc384b18da..cabab6c80bf5 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -49,8 +49,9 @@ public class InsetsSource implements Parcelable { /** The insets source ID of IME */ public static final int ID_IME = createId(null, 0, ime()); + /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */ - static final int ID_IME_CAPTION_BAR = + public static final int ID_IME_CAPTION_BAR = InsetsSource.createId(null /* owner */, 1 /* index */, captionBar()); /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d5b4e4187fe4..7b456007e4ae 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3991,9 +3991,7 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); - mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - mLastPreferredFrameRate = mPreferredFrameRate; mPreferredFrameRate = 0; } @@ -11982,8 +11980,11 @@ public final class ViewRootImpl implements ViewParent, ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; try { - mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, - frameRateCategory, false).apply(); + if (mLastPreferredFrameRateCategory != frameRateCategory) { + mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, + frameRateCategory, false).applyAsyncUnsafe(); + mLastPreferredFrameRateCategory = frameRateCategory; + } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate category", e); } @@ -12003,8 +12004,11 @@ public final class ViewRootImpl implements ViewParent, } try { - mFrameRateTransaction.setFrameRate(mSurfaceControl, - preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); + if (mLastPreferredFrameRate != preferredFrameRate) { + mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, + Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe(); + mLastPreferredFrameRate = preferredFrameRate; + } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 92ce9f7e5d4c..046ea77f196d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3301,9 +3301,8 @@ public interface WindowManager extends ViewManager { /** * An internal annotation for flags that can be specified to {@link #softInputMode}. * - * @hide + * @removed mistakenly exposed as system-api previously */ - @SystemApi @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = { SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 42a1f1e58967..11bd22f7d6ff 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -37,5 +37,6 @@ flag { namespace: "window_surfaces" name: "remove_capture_display" description: "Remove uses of ScreenCapture#captureDisplay" + is_fixed_read_only: true bug: "293445881" }
\ No newline at end of file diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 755113b22088..0dbdb36977f4 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -690,18 +690,6 @@ public final class ProcessState { } } - public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) { - ensureNotDead(); - mCommonProcess.addCachedKill(1, pss, pss, pss); - if (!mCommonProcess.mMultiPackage) { - return; - } - - for (int ip=pkgList.size()-1; ip>=0; ip--) { - pullFixedProc(pkgList, ip).addCachedKill(1, pss, pss, pss); - } - } - public ProcessState pullFixedProc(String pkgName) { if (mMultiPackage) { // The array map is still pointing to a common process state diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index df6c1538fc6d..e3bb1fe8b736 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -66,10 +66,6 @@ public class SystemUiSystemPropertiesFlags { public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI = releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi"); - /** Gating the redaction of OTP notifications on the lockscreen */ - public static final Flag OTP_REDACTION = - devFlag("persist.sysui.notification.otp_redaction"); - /** Gating the logging of DND state change events. */ public static final Flag LOG_DND_STATE_EVENTS = releasedFlag("persist.sysui.notification.log_dnd_state_events"); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 3977666627b7..fc60f065a965 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -90,7 +90,7 @@ interface IStatusBarService void onNotificationSettingsViewed(String key); void onNotificationBubbleChanged(String key, boolean isBubble, int flags); void onBubbleMetadataFlagChanged(String key, int flags); - void hideCurrentInputMethodForBubbles(); + void hideCurrentInputMethodForBubbles(int displayId); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); oneway void clearInlineReplyUriPermissions(String key); void onNotificationFeedbackReceived(String key, in Bundle feedback); diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp index aebe7ea7ee61..95bf49fe501e 100644 --- a/core/jni/android_os_PerformanceHintManager.cpp +++ b/core/jni/android_os_PerformanceHintManager.cpp @@ -16,16 +16,15 @@ #define LOG_TAG "PerfHint-jni" -#include <android/performance_hint.h> +#include "jni.h" + #include <dlfcn.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <utils/Log.h> - #include <vector> #include "core_jni_helpers.h" -#include "jni.h" namespace android { @@ -45,11 +44,6 @@ typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t); typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t); typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const); typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool); -typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*); - -typedef AWorkDuration* (*AWD_create)(); -typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t); -typedef void (*AWD_release)(AWorkDuration*); bool gAPerformanceHintBindingInitialized = false; APH_getManager gAPH_getManagerFn = nullptr; @@ -62,14 +56,6 @@ APH_sendHint gAPH_sendHintFn = nullptr; APH_setThreads gAPH_setThreadsFn = nullptr; APH_getThreadIds gAPH_getThreadIdsFn = nullptr; APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr; -APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr; - -AWD_create gAWD_createFn = nullptr; -AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr; -AWD_release gAWD_releaseFn = nullptr; void ensureAPerformanceHintBindingInitialized() { if (gAPerformanceHintBindingInitialized) return; @@ -126,46 +112,9 @@ void ensureAPerformanceHintBindingInitialized() { (APH_setPreferPowerEfficiency)dlsym(handle_, "APerformanceHint_setPreferPowerEfficiency"); LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr, - "Failed to find required symbol " + "Failed to find required symbol" "APerformanceHint_setPreferPowerEfficiency!"); - gAPH_reportActualWorkDuration2Fn = - (APH_reportActualWorkDuration2)dlsym(handle_, - "APerformanceHint_reportActualWorkDuration2"); - LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr, - "Failed to find required symbol " - "APerformanceHint_reportActualWorkDuration2!"); - - gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create"); - LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr, - "Failed to find required symbol AWorkDuration_create!"); - - gAWD_setWorkPeriodStartTimestampNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setWorkPeriodStartTimestampNanos!"); - - gAWD_setActualTotalDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setActualTotalDurationNanos!"); - - gAWD_setActualCpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!"); - - gAWD_setActualGpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!"); - - gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release"); - LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr, - "Failed to find required symbol AWorkDuration_release!"); - gAPerformanceHintBindingInitialized = true; } @@ -289,25 +238,6 @@ static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nati enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, - jlong workPeriodStartTimestampNanos, - jlong actualTotalDurationNanos, - jlong actualCpuDurationNanos, - jlong actualGpuDurationNanos) { - ensureAPerformanceHintBindingInitialized(); - - AWorkDuration* workDuration = gAWD_createFn(); - gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos); - gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos); - gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos); - gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos); - - gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), - workDuration); - - gAWD_releaseFn(workDuration); -} - static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeAcquireManager", "()J", (void*)nativeAcquireManager}, {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos}, @@ -319,7 +249,6 @@ static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds}, {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency}, - {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2}, }; int register_android_os_PerformanceHintManager(JNIEnv* env) { diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 18c60a793166..91dfc6023e42 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -1245,7 +1245,7 @@ jint android_os_Process_sendSignalToProcessGroup(JNIEnv* env, jobject clazz, jin void android_os_Process_removeAllProcessGroups(JNIEnv* env, jobject clazz) { - return removeAllProcessGroups(); + return removeAllEmptyProcessGroups(); } static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) { diff --git a/core/res/Android.bp b/core/res/Android.bp index 4e686b7ad80c..34c404520a2b 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -152,6 +152,8 @@ android_app { "simulated_device_launcher", ], }, + + generate_product_characteristics_rro: true, } java_genrule { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 926e0fa2f844..7b075e6f4872 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -822,6 +822,7 @@ <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" /> <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" /> + <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" /> <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f827d28b26fd..6cd6eb4b8df9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5465,6 +5465,7 @@ <item>1,1,1.0,.15,15</item> <item>0,0,0.7,0,1</item> <item>0,0,0.83333,0,1</item> + <item>0,0,1.1667,0,1</item> </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> @@ -6806,6 +6807,10 @@ <!-- Whether or not ActivityManager PSS profiling is disabled. --> <bool name="config_am_disablePssProfiling">false</bool> + <!-- The modifier used to adjust AM's PSS threshold for downgrading services to service B if + RSS is being collected instead. --> + <item name="config_am_pssToRssThresholdModifier" format="float" type="dimen">1.5</item> + <!-- Whether unlocking and waking a device are sequenced --> <bool name="config_orderUnlockAndWake">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f3aa936736ab..24b39bc1225e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5270,6 +5270,7 @@ <!-- For ActivityManager PSS profiling configurability --> <java-symbol type="bool" name="config_am_disablePssProfiling" /> + <java-symbol type="dimen" name="config_am_pssToRssThresholdModifier" /> <java-symbol type="raw" name="default_ringtone_vibration_effect" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index 443dcb4e51c5..2315a58eb487 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,6 +60,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.Collections; @@ -80,20 +83,32 @@ import java.util.stream.Collectors; @Presubmit public class TransactionExecutorTests { + @Mock + private ClientTransactionHandler mTransactionHandler; + @Mock + private ActivityLifecycleItem mActivityLifecycleItem; + @Mock + private IBinder mActivityToken; + @Mock + private Activity mActivity; + private TransactionExecutor mExecutor; private TransactionExecutorHelper mExecutorHelper; - private ClientTransactionHandler mTransactionHandler; private ActivityClientRecord mClientRecord; @Before public void setUp() throws Exception { - mTransactionHandler = mock(ClientTransactionHandler.class); + MockitoAnnotations.initMocks(this); mClientRecord = new ActivityClientRecord(); when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord); mExecutor = spy(new TransactionExecutor(mTransactionHandler)); mExecutorHelper = new TransactionExecutorHelper(); + + doReturn(true).when(mActivityLifecycleItem).isActivityLifecycleItem(); + doReturn(mActivityToken).when(mActivityLifecycleItem).getActivityToken(); + doReturn(mActivity).when(mTransactionHandler).getActivity(mActivityToken); } @Test @@ -229,23 +244,21 @@ public class TransactionExecutorTests { when(callback1.getPostExecutionState()).thenReturn(UNDEFINED); ClientTransactionItem callback2 = mock(ClientTransactionItem.class); when(callback2.getPostExecutionState()).thenReturn(UNDEFINED); - ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); - IBinder token = mock(IBinder.class); - when(stateRequest.getActivityToken()).thenReturn(token); - when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class)); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addCallback(callback1); transaction.addCallback(callback2); - transaction.setLifecycleStateRequest(stateRequest); + transaction.setLifecycleStateRequest(mActivityLifecycleItem); transaction.preExecute(mTransactionHandler); mExecutor.execute(transaction); - InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest); + InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, + mActivityLifecycleItem); inOrder.verify(callback1).execute(eq(mTransactionHandler), any()); inOrder.verify(callback2).execute(eq(mTransactionHandler), any()); - inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any()); + inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord), + any()); } @Test @@ -254,23 +267,21 @@ public class TransactionExecutorTests { when(callback1.getPostExecutionState()).thenReturn(UNDEFINED); ClientTransactionItem callback2 = mock(ClientTransactionItem.class); when(callback2.getPostExecutionState()).thenReturn(UNDEFINED); - ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); - IBinder token = mock(IBinder.class); - when(stateRequest.getActivityToken()).thenReturn(token); - when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class)); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(callback1); transaction.addTransactionItem(callback2); - transaction.addTransactionItem(stateRequest); + transaction.addTransactionItem(mActivityLifecycleItem); transaction.preExecute(mTransactionHandler); mExecutor.execute(transaction); - InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest); + InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, + mActivityLifecycleItem); inOrder.verify(callback1).execute(eq(mTransactionHandler), any()); inOrder.verify(callback2).execute(eq(mTransactionHandler), any()); - inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any()); + inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord), + any()); } @Test @@ -536,42 +547,36 @@ public class TransactionExecutorTests { @Test public void testActivityItemExecute() { - final IBinder token = mock(IBinder.class); final ClientTransaction transaction = ClientTransaction.obtain(null /* client */); final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class); when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED); - when(activityItem.getActivityToken()).thenReturn(token); + when(activityItem.getActivityToken()).thenReturn(mActivityToken); transaction.addCallback(activityItem); - final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); - transaction.setLifecycleStateRequest(stateRequest); - when(stateRequest.getActivityToken()).thenReturn(token); - when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class)); + transaction.setLifecycleStateRequest(mActivityLifecycleItem); mExecutor.execute(transaction); - final InOrder inOrder = inOrder(activityItem, stateRequest); + final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem); inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any()); - inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any()); + inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord), + any()); } @Test public void testExecuteTransactionItems_activityItemExecute() { - final IBinder token = mock(IBinder.class); final ClientTransaction transaction = ClientTransaction.obtain(null /* client */); final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class); when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED); - when(activityItem.getActivityToken()).thenReturn(token); + when(activityItem.getActivityToken()).thenReturn(mActivityToken); transaction.addTransactionItem(activityItem); - final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); - transaction.addTransactionItem(stateRequest); - when(stateRequest.getActivityToken()).thenReturn(token); - when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class)); + transaction.addTransactionItem(mActivityLifecycleItem); mExecutor.execute(transaction); - final InOrder inOrder = inOrder(activityItem, stateRequest); + final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem); inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any()); - inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any()); + inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord), + any()); } private static int[] shuffledArray(int[] inputArray) { diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 9b4dec4118a1..20ba4270e6fc 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -182,42 +182,4 @@ public class PerformanceHintManagerTest { s.setPreferPowerEfficiency(true); s.setPreferPowerEfficiency(true); } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6)); - s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20)); - s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6)); - } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1)); - }); - } } diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index 9595332afc6c..e1bcd4a0727b 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsSource.ID_IME_CAPTION_BAR; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.SIZE; @@ -52,12 +53,15 @@ public class InsetsSourceTest { private final InsetsSource mSource = new InsetsSource(0 /* id */, navigationBars()); private final InsetsSource mImeSource = new InsetsSource(1 /* id */, ime()); + private final InsetsSource mImeCaptionSource = new InsetsSource( + ID_IME_CAPTION_BAR, captionBar()); private final InsetsSource mCaptionSource = new InsetsSource(2 /* id */, captionBar()); @Before public void setUp() { mSource.setVisible(true); mImeSource.setVisible(true); + mImeCaptionSource.setVisible(true); mCaptionSource.setVisible(true); } @@ -110,6 +114,18 @@ public class InsetsSourceTest { } @Test + public void testCalculateInsets_imeCaptionBar() { + mImeCaptionSource.setFrame(new Rect(0, 400, 500, 500)); + Insets insets = mImeCaptionSource.calculateInsets(new Rect(0, 0, 500, 500), false); + assertEquals(Insets.of(0, 0, 0, 100), insets); + + // Place caption bar at top; IME caption bar must always return bottom insets + mImeCaptionSource.setFrame(new Rect(0, 0, 500, 100)); + insets = mImeCaptionSource.calculateInsets(new Rect(0, 0, 500, 500), false); + assertEquals(Insets.of(0, 0, 0, 100), insets); + } + + @Test public void testCalculateInsets_caption_resizing() { mCaptionSource.setFrame(new Rect(0, 0, 100, 100)); Insets insets = mCaptionSource.calculateInsets(new Rect(0, 0, 200, 200), false); diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp index 580e73c331a1..06340a222ac6 100644 --- a/core/tests/utiltests/Android.bp +++ b/core/tests/utiltests/Android.bp @@ -35,6 +35,7 @@ android_test { "androidx.test.ext.junit", "truth", "servicestests-utils", + "ravenwood-junit", ], libs: [ @@ -50,3 +51,22 @@ android_test { test_suites: ["device-tests"], } + +android_ravenwood_test { + name: "FrameworksUtilTestsRavenwood", + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + ], + srcs: [ + "src/android/util/DataUnitTest.java", + "src/android/util/EventLogTest.java", + "src/android/util/IndentingPrintWriterTest.java", + "src/android/util/IntArrayTest.java", + "src/android/util/LocalLogTest.java", + "src/android/util/LongArrayTest.java", + "src/android/util/SlogTest.java", + "src/android/util/TimeUtilsTest.java", + ], + auto_gen_config: true, +} diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/utiltests/src/android/util/DataUnitTest.java index 034cbddc0144..af9ebc833d4b 100644 --- a/core/tests/coretests/src/android/util/DataUnitTest.java +++ b/core/tests/utiltests/src/android/util/DataUnitTest.java @@ -16,12 +16,18 @@ package android.util; +import static org.junit.Assert.assertEquals; + import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest -public class DataUnitTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class DataUnitTest { + @Test public void testSi() throws Exception { assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12)); assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12)); @@ -29,6 +35,7 @@ public class DataUnitTest extends TestCase { assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12)); } + @Test public void testIec() throws Exception { assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12)); assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12)); diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/utiltests/src/android/util/EventLogTest.java index 94e72c4a8d52..0ebf2c163d19 100644 --- a/core/tests/coretests/src/android/util/EventLogTest.java +++ b/core/tests/utiltests/src/android/util/EventLogTest.java @@ -16,23 +16,42 @@ package android.util; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.EventLog.Event; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import junit.framework.AssertionFailedError; +import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; /** Unit tests for {@link android.util.EventLog} */ +@SmallTest +@RunWith(AndroidJUnit4.class) public class EventLogTest { + @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testSimple() throws Throwable { + EventLog.writeEvent(42, 42); + EventLog.writeEvent(42, 42L); + EventLog.writeEvent(42, 42f); + EventLog.writeEvent(42, "forty-two"); + EventLog.writeEvent(42, 42, "forty-two", null, 42); + } @Test + @IgnoreUnderRavenwood(reason = "Reading not yet supported") public void testWithNewData() throws Throwable { Event event = createEvent(() -> { EventLog.writeEvent(314, 123); diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/utiltests/src/android/util/LocalLogTest.java index d4861cd391a8..5025a3d0980c 100644 --- a/core/tests/coretests/src/android/util/LocalLogTest.java +++ b/core/tests/utiltests/src/android/util/LocalLogTest.java @@ -16,9 +16,13 @@ package android.util; +import static org.junit.Assert.assertTrue; + import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.PrintWriter; import java.io.StringWriter; @@ -27,13 +31,16 @@ import java.util.Collections; import java.util.List; @LargeTest -public class LocalLogTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class LocalLogTest { + @Test public void testA_localTimestamps() { boolean localTimestamps = true; doTestA(localTimestamps); } + @Test public void testA_nonLocalTimestamps() { boolean localTimestamps = false; doTestA(localTimestamps); @@ -49,6 +56,7 @@ public class LocalLogTest extends TestCase { testcase(new LocalLog(10, localTimestamps), lines, want); } + @Test public void testB() { String[] lines = { "foo", @@ -59,6 +67,7 @@ public class LocalLogTest extends TestCase { testcase(new LocalLog(0), lines, want); } + @Test public void testC() { String[] lines = { "dropped", diff --git a/core/tests/utiltests/src/android/util/SlogTest.java b/core/tests/utiltests/src/android/util/SlogTest.java new file mode 100644 index 000000000000..6f761e348dbe --- /dev/null +++ b/core/tests/utiltests/src/android/util/SlogTest.java @@ -0,0 +1,47 @@ +/* + * 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.util; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SlogTest { + private static final String TAG = "tag"; + private static final String MSG = "msg"; + private static final Throwable THROWABLE = new Throwable(); + + @Test + public void testSimple() { + Slog.v(TAG, MSG); + Slog.d(TAG, MSG); + Slog.i(TAG, MSG); + Slog.w(TAG, MSG); + Slog.e(TAG, MSG); + } + + @Test + public void testThrowable() { + Slog.v(TAG, MSG, THROWABLE); + Slog.d(TAG, MSG, THROWABLE); + Slog.i(TAG, MSG, THROWABLE); + Slog.w(TAG, MSG, THROWABLE); + Slog.e(TAG, MSG, THROWABLE); + } +} diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java new file mode 100644 index 000000000000..e8246c83e086 --- /dev/null +++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java @@ -0,0 +1,117 @@ +/* + * 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.util; + +import static org.junit.Assert.assertEquals; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class TimeUtilsTest { + public static final long SECOND_IN_MILLIS = 1000; + public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; + public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; + public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; + public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; + + @Test + public void testFormatTime() { + assertEquals("1672556400000 (now)", + TimeUtils.formatTime(1672556400000L, 1672556400000L)); + assertEquals("1672556400000 (in 10 ms)", + TimeUtils.formatTime(1672556400000L, 1672556400000L - 10)); + assertEquals("1672556400000 (10 ms ago)", + TimeUtils.formatTime(1672556400000L, 1672556400000L + 10)); + + // Uses formatter above, so we just care that it doesn't crash + TimeUtils.formatRealtime(1672556400000L); + TimeUtils.formatUptime(1672556400000L); + } + + @Test + public void testFormatDuration_Zero() { + assertEquals("0", TimeUtils.formatDuration(0)); + } + + @Test + public void testFormatDuration_Negative() { + assertEquals("-10ms", TimeUtils.formatDuration(-10)); + } + + @Test + public void testFormatDuration() { + long accum = 900; + assertEquals("+900ms", TimeUtils.formatDuration(accum)); + + accum += 59 * SECOND_IN_MILLIS; + assertEquals("+59s900ms", TimeUtils.formatDuration(accum)); + + accum += 59 * MINUTE_IN_MILLIS; + assertEquals("+59m59s900ms", TimeUtils.formatDuration(accum)); + + accum += 23 * HOUR_IN_MILLIS; + assertEquals("+23h59m59s900ms", TimeUtils.formatDuration(accum)); + + accum += 6 * DAY_IN_MILLIS; + assertEquals("+6d23h59m59s900ms", TimeUtils.formatDuration(accum)); + } + + @Test + public void testDumpTime() { + assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> { + TimeUtils.dumpTime(pw, 1672556400000L); + })); + assertEquals("2023-01-01 00:00:00.000 (now)", runWithPrintWriter((pw) -> { + TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L); + })); + assertEquals("2023-01-01 00:00:00.000 (-10ms)", runWithPrintWriter((pw) -> { + TimeUtils.dumpTimeWithDelta(pw, 1672556400000L, 1672556400000L + 10); + })); + } + + @Test + public void testFormatForLogging() { + assertEquals("unknown", TimeUtils.formatForLogging(0)); + assertEquals("unknown", TimeUtils.formatForLogging(-1)); + assertEquals("unknown", TimeUtils.formatForLogging(Long.MIN_VALUE)); + assertEquals("2023-01-01 00:00:00", TimeUtils.formatForLogging(1672556400000L)); + } + + @Test + public void testLogTimeOfDay() { + assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); + } + + public static String runWithPrintWriter(Consumer<PrintWriter> consumer) { + final StringWriter sw = new StringWriter(); + consumer.accept(new PrintWriter(sw)); + return sw.toString(); + } + + public static String runWithStringBuilder(Consumer<StringBuilder> consumer) { + final StringBuilder sb = new StringBuilder(); + consumer.accept(sb); + return sb.toString(); + } +} diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java index 07f1d4a09006..8dc9579e6b52 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java @@ -63,7 +63,7 @@ public class HideInCommentsChecker extends BugChecker implements @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { - final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree); + final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree, state); final String sourceCode = state.getSourceCode().toString(); for (ErrorProneToken token : ErrorProneTokens.getTokens(sourceCode, state.context)) { for (Tokens.Comment comment : token.comments()) { @@ -112,9 +112,9 @@ public class HideInCommentsChecker extends BugChecker implements } - private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree) { + private Map<Integer, Tree> findJavadocableTrees(CompilationUnitTree tree, VisitorState state) { Map<Integer, Tree> javadoccableTrees = new HashMap<>(); - new SuppressibleTreePathScanner<Void, Void>() { + new SuppressibleTreePathScanner<Void, Void>(state) { @Override public Void visitClass(ClassTree classTree, Void unused) { javadoccableTrees.put(getStartPosition(classTree), classTree); diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 2ec4524e1241..d659ddd75f72 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -402,8 +402,8 @@ public class BaseRecordingCanvas extends Canvas { } @Override - public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, - @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + public final void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, + @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { nDrawDoubleRoundRect(mNativeCanvasWrapper, outer.left, outer.top, outer.right, outer.bottom, outerRadii, inner.left, inner.top, inner.right, inner.bottom, innerRadii, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 4d73c20fe39f..ca3d8d18db83 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -375,7 +375,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { return TaskFragmentAnimationParams.DEFAULT; } return new TaskFragmentAnimationParams.Builder() - .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) + // TODO(b/263047900): Update extensions API. + // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) .build(); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index faf7c3999402..b5c32bbe78fa 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -854,7 +854,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return new SplitAttributes.Builder() .setSplitType(splitTypeToUpdate) .setLayoutDirection(splitAttributes.getLayoutDirection()) - .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) + // TODO(b/263047900): Update extensions API. + // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor()) .build(); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index 9607b78bacf0..60beb0b7f0a4 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -17,6 +17,7 @@ package androidx.window.extensions; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static com.google.common.truth.Truth.assertThat; import android.app.ActivityTaskManager; @@ -69,6 +70,7 @@ public class WindowExtensionsTest { .isEqualTo(SplitAttributes.LayoutDirection.LOCALE); assertThat(splitAttributes.getSplitType()) .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f)); - assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0); + // TODO(b/263047900): Update extensions API. + // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0); } } diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index c366ccd235db..4d2d960822d1 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -42,3 +42,11 @@ flag { description: "Enables PiP UI state callback on entering" bug: "303718131" } + +flag { + name: "enable_pip2_implementation" + namespace: "multitasking" + description: "Enables the new implementation of PiP (PiP2)" + bug: "290220798" + is_fixed_read_only: true +} diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 6a6f2b02766d..e4abae48c8fd 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -141,4 +141,7 @@ window. If false, the splash screen will be a solid color splash screen whenever the app has not provided a windowSplashScreenAnimatedIcon. --> <bool name="config_canUseAppIconForSplashScreen">true</bool> + + <!-- Whether CompatUIController is enabled --> + <bool name="config_enableCompatUIController">true</bool> </resources> diff --git a/libs/WindowManager/Shell/res/values/config_tv.xml b/libs/WindowManager/Shell/res/values/config_tv.xml new file mode 100644 index 000000000000..3da5539c9ae6 --- /dev/null +++ b/libs/WindowManager/Shell/res/values/config_tv.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources> + <integer name="config_tvPipEnterFadeOutDuration">500</integer> + <integer name="config_tvPipEnterFadeInDuration">1500</integer> + <integer name="config_tvPipExitFadeOutDuration">500</integer> + <integer name="config_tvPipExitFadeInDuration">500</integer> +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index f0da35df39ee..3e113276027e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -553,8 +553,9 @@ public class BubbleController implements ConfigurationChangeListener, * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. */ void hideCurrentInputMethod() { + int displayId = mWindowManager.getDefaultDisplay().getDisplayId(); try { - mBarService.hideCurrentInputMethodForBubbles(); + mBarService.hideCurrentInputMethodForBubbles(displayId); } catch (RemoteException e) { e.printStackTrace(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index d520ff791e07..8b6c7b663f82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -258,7 +258,7 @@ public class PipBoundsState { ActivityTaskManager.getService().onPictureInPictureStateChanged( new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */) ); - } catch (RemoteException e) { + } catch (RemoteException | IllegalStateException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Unable to set alert PiP state change.", TAG); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index 108aa8275009..1e30d8feb132 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -21,13 +21,13 @@ import android.app.WindowConfiguration import android.content.ComponentName import android.content.Context import android.os.RemoteException -import android.os.SystemProperties import android.util.DisplayMetrics import android.util.Log import android.util.Pair import android.util.TypedValue import android.window.TaskSnapshot import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs @@ -37,7 +37,6 @@ object PipUtils { // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. private const val EPSILON = 1e-7 - private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation" /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. @@ -138,5 +137,5 @@ object PipUtils { @JvmStatic val isPip2ExperimentEnabled: Boolean - get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false) + get() = Flags.enablePip2Implementation() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 54cf84c32276..27dc870e81ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -221,34 +221,57 @@ public abstract class WMShellBaseModule { Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, - CompatUIController compatUI, + Optional<CompatUIController> compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional, - @ShellMainThread ShellExecutor mainExecutor - ) { + @ShellMainThread ShellExecutor mainExecutor) { if (!context.getResources().getBoolean(R.bool.config_registerShellTaskOrganizerOnInit)) { // TODO(b/238217847): Force override shell init if registration is disabled shellInit = new ShellInit(mainExecutor); } - return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI, - unfoldAnimationController, recentTasksOptional, mainExecutor); + return new ShellTaskOrganizer( + shellInit, + shellCommandHandler, + compatUI.orElse(null), + unfoldAnimationController, + recentTasksOptional, + mainExecutor); } @WMSingleton @Provides - static CompatUIController provideCompatUIController(Context context, + static Optional<CompatUIController> provideCompatUIController( + Context context, ShellInit shellInit, ShellController shellController, - DisplayController displayController, DisplayInsetsController displayInsetsController, - DisplayImeController imeController, SyncTransactionQueue syncQueue, - @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration, + DisplayController displayController, + DisplayInsetsController displayInsetsController, + DisplayImeController imeController, + SyncTransactionQueue syncQueue, + @ShellMainThread ShellExecutor mainExecutor, + Lazy<Transitions> transitionsLazy, + DockStateReader dockStateReader, + CompatUIConfiguration compatUIConfiguration, CompatUIShellCommandHandler compatUIShellCommandHandler, AccessibilityManager accessibilityManager) { - return new CompatUIController(context, shellInit, shellController, displayController, - displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy, - dockStateReader, compatUIConfiguration, compatUIShellCommandHandler, - accessibilityManager); + if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) { + return Optional.empty(); + } + return Optional.of( + new CompatUIController( + context, + shellInit, + shellController, + displayController, + displayInsetsController, + imeController, + syncQueue, + mainExecutor, + transitionsLazy, + dockStateReader, + compatUIConfiguration, + compatUIShellCommandHandler, + accessibilityManager)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index a9675f976fa9..1947097c2f15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.Handler; import android.os.SystemClock; +import androidx.annotation.NonNull; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -41,7 +43,6 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsController; @@ -78,11 +79,12 @@ public abstract class TvPipModule { PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, PipMediaController pipMediaController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, TvPipNotificationController tvPipNotificationController, TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, @@ -99,9 +101,10 @@ public abstract class TvPipModule { pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, - pipTransitionController, + tvPipTransition, tvPipMenuController, pipMediaController, tvPipNotificationController, @@ -151,25 +154,23 @@ public abstract class TvPipModule { return new LegacySizeSpecSource(context, pipDisplayLayoutState); } - // Handler needed for loadDrawableAsync() in PipControlsViewController @WMSingleton @Provides - static PipTransitionController provideTvPipTransition( + static TvPipTransition provideTvPipTransition( Context context, - ShellInit shellInit, - ShellTaskOrganizer shellTaskOrganizer, - Transitions transitions, + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, - TvPipMenuController pipMenuController, + TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + PipDisplayLayoutState pipDisplayLayoutState) { return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions, - tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - Optional.empty()); + tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState, + pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState); } @WMSingleton @@ -207,7 +208,7 @@ public abstract class TvPipModule { PipTransitionState pipTransitionState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipAnimationController pipAnimationController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, PipParamsChangedForwarder pipParamsChangedForwarder, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenControllerOptional, @@ -217,7 +218,7 @@ public abstract class TvPipModule { return new TvPipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController, - pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenControllerOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index cbed4b5a501f..a58d94ecd19b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -81,15 +81,35 @@ public class PipSurfaceTransactionHelper { */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds) { return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */); } /** - * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * Operates the scale (setMatrix) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); // We want the matrix to position the surface relative to the screen coordinates so offset // the source to 0,0 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c05601b3f04f..b4067d0db112 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -297,9 +297,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // changed RunningTaskInfo when it finishes. private ActivityManager.RunningTaskInfo mDeferredTaskInfo; private WindowContainerToken mToken; - private SurfaceControl mLeash; + protected SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; private IntConsumer mOnDisplayIdChangeCallback; @@ -973,7 +973,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - cancelCurrentAnimator(); + cancelAnimationOnTaskVanished(); onExitPipFinished(info); if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -981,6 +981,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } + protected void cancelAnimationOnTaskVanished() { + cancelCurrentAnimator(); + } + @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken"); @@ -1100,7 +1104,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** Called when exiting PIP transition is finished to do the state cleanup. */ - void onExitPipFinished(TaskInfo info) { + public void onExitPipFinished(TaskInfo info) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onExitPipFinished: %s, state=%s leash=%s", info.topActivity, mPipTransitionState, mLeash); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index a48e969fde35..72c0cd71f198 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import java.util.Collections; import java.util.Set; /** @@ -101,12 +102,29 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed(); if (isPipExpanded) { - updateGravityOnExpansionToggled(/* expanding= */ true); + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); } + @Override + public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG); + + updateExpandedPipSize(); + final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 + && !mTvPipBoundsState.isTvPipManuallyCollapsed(); + if (isPipExpanded) { + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); + } + mTvPipBoundsState.setTvPipExpanded(isPipExpanded); + return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(), + Collections.emptySet()).getUnstashedBounds()); + } + /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @Override public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { @@ -133,16 +151,25 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { */ @NonNull public Placement getTvPipPlacement() { + final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); + final Set<Rect> unrestrictedKeepClearAreas = + mTvPipBoundsState.getUnrestrictedKeepClearAreas(); + + return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas); + } + + /** + * Calculates the PiP bounds. + */ + @NonNull + private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas, + Set<Rect> unrestrictedKeepClearAreas) { final Size pipSize = getPipSize(); final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); - final Set<Rect> unrestrictedKeepClearAreas = - mTvPipBoundsState.getUnrestrictedKeepClearAreas(); - mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity()); mKeepClearAlgorithm.setScreenSize(screenSize); mKeepClearAlgorithm.setMovementBounds(insetBounds); @@ -189,8 +216,11 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { int updatedGravity; if (expanding) { - // Save collapsed gravity. - mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity()); + if (!mTvPipBoundsState.isTvPipExpanded()) { + // Save collapsed gravity. + mTvPipBoundsState.setTvPipPreviousCollapsedGravity( + mTvPipBoundsState.getTvPipGravity()); + } if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { updatedGravity = Gravity.CENTER_HORIZONTAL | currentY; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index 2b3a93e3c3e8..5ee3734e371d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -131,6 +131,7 @@ public class TvPipBoundsState extends PipBoundsState { mTvFixedPipOrientation = ORIENTATION_UNDETERMINED; mTvPipGravity = mDefaultGravity; mPreviousCollapsedGravity = mDefaultGravity; + mIsTvPipExpanded = false; mTvPipManuallyCollapsed = false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 72115fdefa05..cd3d38b6500c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -56,6 +56,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; @@ -122,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipDisplayLayoutState mPipDisplayLayoutState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsController mTvPipBoundsController; + private final PipTransitionState mPipTransitionState; private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; @@ -157,6 +159,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -177,6 +180,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -199,6 +203,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -212,6 +217,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Handler mainHandler, ShellExecutor mainExecutor) { mContext = context; + mPipTransitionState = pipTransitionState; mMainHandler = mainHandler; mMainExecutor = mainExecutor; mShellController = shellController; @@ -365,7 +371,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState)); mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); - onPipDisappeared(); } private void togglePipExpansion() { @@ -420,6 +425,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) { + if (!mPipTransitionState.hasEnteredPip()) { + // Do not schedule a move animation while we're still transitioning into/out of PiP + return; + } + mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds, animationDuration, null); mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds); @@ -447,7 +457,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal return; } mPipTaskOrganizer.removePip(); - onPipDisappeared(); + mTvPipMenuController.closeMenu(); } @Override @@ -477,7 +487,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mActionBroadcastReceiver.unregister(); - mTvPipMenuController.closeMenu(); + mTvPipMenuController.detach(); mTvPipActionsProvider.reset(); mTvPipBoundsState.resetTvPipState(); mTvPipBoundsController.reset(); @@ -501,8 +511,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onPipTransitionCanceled(int direction) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); - mTvPipMenuController.onPipTransitionFinished( - PipAnimationController.isInPipDirection(direction)); mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index ee55211a73a9..c6803f7beebd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -262,8 +262,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void detach() { - closeMenu(); detachPipMenu(); + switchToMenuMode(MODE_NO_MENU); mLeash = null; } @@ -320,10 +320,21 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx, Rect pipBounds, float alpha) { + movePipMenu(pipTx, pipBounds, alpha); + } + + /** + * Move the PiP menu with the given bounds and update its opacity. + * The PiP SurfaceControl is given if there is a need to synchronize the movements + * on the same frame as PiP. + */ + public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds, + float alpha) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha); + "%s: movePipMenu: %s, alpha %s", TAG, + pipBounds != null ? pipBounds.toShortString() : null, alpha); - if (pipBounds.isEmpty()) { + if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) { if (pipTx == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: no transaction given", TAG); @@ -334,28 +345,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return; } - final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); - final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); - final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); if (pipTx == null) { pipTx = new SurfaceControl.Transaction(); } - pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); - pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + + final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); + final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); + + if (pipBounds != null) { + final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); + pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); + pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + updateMenuBounds(pipBounds); + } if (alpha != ALPHA_NO_CHANGE) { pipTx.setAlpha(frontSurface, alpha); pipTx.setAlpha(backSurface, alpha); } - // Synchronize drawing the content in the front and back surfaces together with the pip - // transaction and the position change for the front and back surfaces - final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); - syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); - syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); - updateMenuBounds(pipBounds); - syncGroup.addTransaction(pipTx); - syncGroup.markSyncReady(); + if (pipBounds != null) { + // Synchronize drawing the content in the front and back surfaces together with the pip + // transaction and the position change for the front and back surfaces + final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); + syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); + syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); + syncGroup.addTransaction(pipTx); + syncGroup.markSyncReady(); + } else { + pipTx.apply(); + } } private boolean isMenuAttached() { @@ -388,14 +407,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); - mSystemWindows.updateViewLayout(mPipBackgroundView, - getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - mSystemWindows.updateViewLayout(mPipMenuView, - getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - if (mPipMenuView != null) { - mPipMenuView.setPipBounds(pipBounds); + + boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width() + || mPipBackgroundView.getLayoutParams().height != menuBounds.height(); + if (needsRelayout) { + mSystemWindows.updateViewLayout(mPipBackgroundView, + getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + mSystemWindows.updateViewLayout(mPipMenuView, + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + if (mPipMenuView != null) { + mPipMenuView.setPipBounds(pipBounds); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index f86f987039ba..202d36f0dfbd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -168,6 +168,9 @@ class TvPipMenuEduTextDrawer extends FrameLayout { * that the edu text will be marqueed */ private boolean isEduTextMarqueed() { + if (mEduTextView.getLayout() == null) { + return false; + } final int availableWidth = (int) mEduTextView.getWidth() - mEduTextView.getCompoundPaddingLeft() - mEduTextView.getCompoundPaddingRight(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index f315afba9a03..21223c9ac362 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -35,7 +35,6 @@ import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -46,6 +45,7 @@ import java.util.Optional; * TV specific changes to the PipTaskOrganizer. */ public class TvPipTaskOrganizer extends PipTaskOrganizer { + private final TvPipTransition mTvPipTransition; public TvPipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @@ -56,7 +56,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { @NonNull PipMenuController pipMenuController, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, - @NonNull PipTransitionController pipTransitionController, + @NonNull TvPipTransition tvPipTransition, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @@ -65,9 +65,10 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { ShellExecutor mainExecutor) { super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController, - surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); + mTvPipTransition = tvPipTransition; } @Override @@ -105,4 +106,14 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { // when the menu alpha is 0 (e.g. when a fade-in animation starts). return true; } + + @Override + protected void cancelAnimationOnTaskVanished() { + mTvPipTransition.cancelAnimations(); + if (mLeash != null) { + mSurfaceControlTransactionFactory.getTransaction() + .setAlpha(mLeash, 0f) + .apply(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index f24b2b385cad..571c839adf11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -16,43 +16,822 @@ package com.android.wm.shell.pip.tv; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_PIP; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.transitTypeToString; + +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE; +import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; +import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; + +import android.animation.AnimationHandler; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.IBinder; +import android.os.Trace; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import androidx.annotation.FloatRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipTransition; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; -import java.util.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * PiP Transition for TV. */ -public class TvPipTransition extends PipTransition { +public class TvPipTransition extends PipTransitionController { + private static final String TAG = "TvPipTransition"; + private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f; + + private final PipTransitionState mPipTransitionState; + private final PipAnimationController mPipAnimationController; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final TvPipMenuController mTvPipMenuController; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory + mTransactionFactory; + + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + ThreadLocal.withInitial(() -> { + AnimationHandler handler = new AnimationHandler(); + handler.setProvider(new SfVsyncFrameCallbackProvider()); + return handler; + }); + + private final long mEnterFadeOutDuration; + private final long mEnterFadeInDuration; + private final long mExitFadeOutDuration; + private final long mExitFadeInDuration; + + @Nullable + private Animator mCurrentAnimator; + + /** + * The Task window that is currently in PIP windowing mode. + */ + @Nullable + private WindowContainerToken mCurrentPipTaskToken; + + @Nullable + private IBinder mPendingExitTransition; public TvPipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<SplitScreenController> splitScreenOptional) { - super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, - pipDisplayLayoutState, pipTransitionState, tvPipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); + PipDisplayLayoutState pipDisplayLayoutState) { + super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController, + tvPipBoundsAlgorithm); + mPipTransitionState = pipTransitionState; + mPipAnimationController = pipAnimationController; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + mTvPipMenuController = tvPipMenuController; + mPipDisplayLayoutState = pipDisplayLayoutState; + mTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + + mEnterFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeOutDuration); + mEnterFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeInDuration); + mExitFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeOutDuration); + mExitFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeInDuration); + } + + @Override + public void startExitTransition(int type, WindowContainerTransaction out, + @Nullable Rect destinationBounds) { + cancelAnimations(); + mPendingExitTransition = mTransitions.startTransition(type, out, this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (isCloseTransition(info)) { + // PiP is closing (without reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting close animation", TAG); + cancelAnimations(); + startCloseAnimation(info, startTransaction, finishTransaction, finishCallback); + mCurrentPipTaskToken = null; + return true; + + } else if (transition.equals(mPendingExitTransition)) { + // PiP is exiting (reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting exit animation", TAG); + + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + mPendingExitTransition = null; + // PipTaskChange can be null if the PIP task has been detached, for example, when the + // task contains multiple activities, the PIP will be moved to a new PIP task when + // entering, and be moved back when exiting. In that case, the PIP task will be removed + // immediately. + final TaskInfo pipTaskInfo = currentPipTaskChange != null + ? currentPipTaskChange.getTaskInfo() + : mPipOrganizer.getTaskInfo(); + if (pipTaskInfo == null) { + throw new RuntimeException("Cannot find the pip task for exit-pip transition."); + } + + final int type = info.getType(); + switch (type) { + case TRANSIT_EXIT_PIP -> { + TransitionInfo.Change pipChange = currentPipTaskChange; + SurfaceControl activitySc = null; + if (mCurrentPipTaskToken == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG); + } else if (pipChange == null) { + // The pipTaskChange is null, this can happen if we are reparenting the + // PIP activity back to its original Task. In that case, we should animate + // the activity leash instead, which should be the change whose last parent + // is the recorded PiP Task. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getLastParent())) { + // Find the activity that is exiting PiP. + pipChange = change; + activitySc = change.getLeash(); + break; + } + } + } + if (pipChange == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: No window of exiting PIP is found. Can't play expand " + + "animation", + TAG); + removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction, + finishCallback); + return true; + } + final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info); + final SurfaceControl pipLeash; + if (activitySc != null) { + // Use a local leash to animate activity in case the activity has + // letterbox which may be broken by PiP animation, e.g. always end at 0,0 + // in parent and unable to include letterbox area in crop bounds. + final SurfaceControl activitySurface = pipChange.getLeash(); + pipLeash = new SurfaceControl.Builder() + .setName(activitySc + "_pip-leash") + .setContainerLayer() + .setHidden(false) + .setParent(root.getLeash()) + .build(); + startTransaction.reparent(activitySurface, pipLeash); + // Put the activity at local position with offset in case it is letterboxed. + final Point activityOffset = pipChange.getEndRelOffset(); + startTransaction.setPosition(activitySc, activityOffset.x, + activityOffset.y); + } else { + pipLeash = pipChange.getLeash(); + startTransaction.reparent(pipLeash, root.getLeash()); + } + startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); + final Rect currentBounds = mPipBoundsState.getBounds(); + final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); + cancelAnimations(); + startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds, + startTransaction, + finishTransaction, finishCallback); + } + // pass through here is intended + case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo, + startTransaction, finishTransaction, + finishCallback + ); + default -> { + return false; + } + } + mCurrentPipTaskToken = null; + return true; + + } else if (isEnteringPip(info)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting enter animation", TAG); + + // Search for an Enter PiP transition + TransitionInfo.Change enterPip = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { + enterPip = change; + } + } + if (enterPip == null) { + throw new IllegalStateException("Trying to start PiP animation without a pip" + + "participant"); + } + + // Make sure other open changes are visible as entering PIP. Some may be hidden in + // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change == enterPip) continue; + if (TransitionUtil.isOpeningType(change.getMode())) { + final SurfaceControl leash = change.getLeash(); + startTransaction.show(leash).setAlpha(leash, 1.f); + } + } + + cancelAnimations(); + startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); + return true; + } + + return false; + } + + /** + * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. + */ + private void removePipImmediately(@NonNull TransitionInfo info, + @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG); + cancelAnimations(); + startTransaction.apply(); + finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), + mPipDisplayLayoutState.getDisplayBounds()); + mTvPipMenuController.detach(); + mPipOrganizer.onExitPipFinished(taskInfo); + finishCallback.onTransitionFinished(/* wct= */ null); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + } + + private void startCloseAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info); + final SurfaceControl pipLeash = pipTaskChange.getLeash(); + + final List<SurfaceControl> closeLeashes = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) { + closeLeashes.add(change.getLeash()); + } + } + + final Rect pipBounds = mPipBoundsState.getBounds(); + mSurfaceTransactionHelper + .resetScale(startTransaction, pipLeash, pipBounds) + .crop(startTransaction, pipLeash, pipBounds) + .shadow(startTransaction, pipLeash, false); + + final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction(); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + + ValueAnimator closeFadeOutAnimator = createAnimator(); + closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + closeFadeOutAnimator.setDuration(mExitFadeOutDuration); + closeFadeOutAnimator.addUpdateListener( + animationUpdateListener(pipLeash).fadingOut().withMenu()); + for (SurfaceControl leash : closeLeashes) { + closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut()); + } + + closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: start", TAG); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + startTransaction.apply(); + + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: end", TAG); + mTvPipMenuController.detach(); + finishCallback.onTransitionFinished(null /* wct */); + transaction.close(); + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + + mCurrentAnimator = null; + } + }); + + closeFadeOutAnimator.start(); + mCurrentAnimator = closeFadeOutAnimator; + } + + @Override + public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Keep track of the PIP task + mCurrentPipTaskToken = pipChange.getContainer(); + final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); + final SurfaceControl leash = pipChange.getLeash(); + + mTvPipMenuController.attach(leash); + setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, + taskInfo.topActivityInfo); + + final Rect pipBounds = + mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas(); + mPipBoundsState.setBounds(pipBounds); + mTvPipMenuController.movePipMenu(null, pipBounds, 0f); + + final WindowContainerTransaction resizePipWct = new WindowContainerTransaction(); + resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setBounds(taskInfo.token, pipBounds); + + mSurfaceTransactionHelper + .resetScale(finishTransaction, leash, pipBounds) + .crop(finishTransaction, leash, pipBounds) + .shadow(finishTransaction, leash, false); + + final Rect currentBounds = pipChange.getStartAbsBounds(); + final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator enterFadeOutAnimator = createAnimator(); + enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + enterFadeOutAnimator.setDuration(mEnterFadeOutDuration); + enterFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds)); + + enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @SuppressLint("MissingPermission") + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter fade out animation: end", TAG); + SurfaceControl.Transaction tx = mTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .resetScale(tx, leash, pipBounds) + .crop(tx, leash, pipBounds) + .shadow(tx, leash, false); + mShellTaskOrganizer.applyTransaction(resizePipWct); + tx.apply(); + } + }); + + final ValueAnimator enterFadeInAnimator = createAnimator(); + enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + enterFadeInAnimator.setDuration(mEnterFadeInDuration); + enterFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .withMenu() + .atBounds(pipBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet + .play(enterFadeInAnimator) + .after(500) + .after(enterFadeOutAnimator); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: start", TAG); + startTransaction.apply(); + mPipTransitionState.setTransitionState(ENTERING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: cancel", TAG); + enterFadeInAnimator.setCurrentFraction(1f); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: end", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, pipBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(ENTERED_PIP); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; } + private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash, + Rect currentBounds, Rect destinationBounds, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator exitFadeOutAnimator = createAnimator(); + exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + exitFadeOutAnimator.setDuration(mExitFadeOutDuration); + exitFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .withMenu() + .atBounds(currentBounds)); + exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit fade out animation: end", TAG); + startTransaction.apply(); + mPipMenuController.detach(); + } + }); + + final ValueAnimator exitFadeInAnimator = createAnimator(); + exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + exitFadeInAnimator.setDuration(mExitFadeInDuration); + exitFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially( + exitFadeOutAnimator, + exitFadeInAnimator + ); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: start", TAG); + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: end", TAG); + mPipOrganizer.onExitPipFinished(taskInfo); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, destinationBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; + } + + @NonNull + private ValueAnimator createAnimator() { + final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); + return animator; + } + + @NonNull + private TvPipTransitionAnimatorUpdateListener animationUpdateListener( + @NonNull SurfaceControl leash) { + return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController, + mTransactionFactory.getTransaction(), mSurfaceTransactionHelper); + } + + @NonNull + private static Rect scaledRect(@NonNull Rect rect, float scale) { + final Rect out = new Rect(rect); + out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2)); + return out; + } + + private boolean isCloseTransition(TransitionInfo info) { + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE; + } + + @Nullable + private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) { + if (mCurrentPipTaskToken == null) { + return null; + } + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getContainer())) { + return change; + } + } + return null; + } + + /** + * Whether we should handle the given {@link TransitionInfo} animation as entering PIP. + */ + private boolean isEnteringPip(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isEnteringPip(change, info.getType())) return true; + } + return false; + } + + /** + * Whether a particular change is a window that is entering pip. + */ + @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) { + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN + || transitType == TRANSIT_CHANGE) { + return true; + } + // Please file a bug to handle the unexpected transition type. + android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" + + transitTypeToString(transitType), new Throwable()); + } + return false; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG); + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.end(); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (requestHasPipEnter(request)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: handle PiP enter request", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + augmentRequest(transition, request, wct); + return wct; + } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) { + // if we receive a TRANSIT_TO_BACK type of request while in PiP + mPendingExitTransition = transition; + + // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls + mPipTransitionState.setTransitionState(EXITING_PIP); + + // return an empty WindowContainerTransaction so that we don't check other handlers + return new WindowContainerTransaction(); + } else { + return null; + } + } + + @Override + public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, + @NonNull WindowContainerTransaction outWCT) { + if (!requestHasPipEnter(request)) { + throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); + } + outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED); + } + + /** + * Cancel any ongoing PiP transitions/animations. + */ + public void cancelAnimations() { + if (mPipAnimationController.isAnimating()) { + mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); + } + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + } + + @Override + public void end() { + if (mCurrentAnimator != null) { + mCurrentAnimator.end(); + } + } + + private static class TvPipTransitionAnimatorUpdateListener implements + ValueAnimator.AnimatorUpdateListener { + private final SurfaceControl mLeash; + private final TvPipMenuController mTvPipMenuController; + private final SurfaceControl.Transaction mTransaction; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final RectF mTmpRectF = new RectF(); + private final Rect mTmpRect = new Rect(); + + private float mStartAlpha = ALPHA_NO_CHANGE; + private float mEndAlpha = ALPHA_NO_CHANGE; + + @Nullable + private Rect mStartBounds; + @Nullable + private Rect mEndBounds; + private Rect mWindowContainerBounds; + private boolean mShowMenu; + + TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash, + @NonNull TvPipMenuController tvPipMenuController, + @NonNull SurfaceControl.Transaction transaction, + @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + mLeash = leash; + mTvPipMenuController = tvPipMenuController; + mTransaction = transaction; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + } + + public TvPipTransitionAnimatorUpdateListener animateAlpha( + @FloatRange(from = 0.0, to = 1.0) float startAlpha, + @FloatRange(from = 0.0, to = 1.0) float endAlpha) { + mStartAlpha = startAlpha; + mEndAlpha = endAlpha; + return this; + } + + public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) { + mStartBounds = startBounds; + mEndBounds = endBounds; + mWindowContainerBounds = windowContainerBounds; + return this; + } + + public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) { + return animateBounds(bounds, bounds, bounds); + } + + public TvPipTransitionAnimatorUpdateListener fadingOut() { + return animateAlpha(1f, 0f); + } + + public TvPipTransitionAnimatorUpdateListener fadingIn() { + return animateAlpha(0f, 1f); + } + + public TvPipTransitionAnimatorUpdateListener withMenu() { + mShowMenu = true; + return this; + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final float fraction = animation.getAnimatedFraction(); + final float alpha = lerp(mStartAlpha, mEndAlpha, fraction); + if (mStartBounds != null && mEndBounds != null) { + lerp(mStartBounds, mEndBounds, fraction, mTmpRectF); + applyAnimatedValue(alpha, mTmpRectF); + } else { + applyAnimatedValue(alpha, null); + } + } + + private void applyAnimatedValue(float alpha, @Nullable RectF bounds) { + Trace.beginSection("applyAnimatedValue"); + final SurfaceControl.Transaction tx = mTransaction; + + Trace.beginSection("leash scale and alpha"); + if (alpha != ALPHA_NO_CHANGE) { + mSurfaceTransactionHelper.alpha(tx, mLeash, alpha); + } + if (bounds != null) { + mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds); + } + mSurfaceTransactionHelper.shadow(tx, mLeash, false); + tx.show(mLeash); + Trace.endSection(); + + if (mShowMenu) { + Trace.beginSection("movePipMenu"); + if (bounds != null) { + mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right, + (int) bounds.bottom); + mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha); + } else { + mTvPipMenuController.movePipMenu(tx, null, alpha); + } + Trace.endSection(); + } else { + mTvPipMenuController.movePipMenu(tx, null, 0f); + } + + tx.apply(); + Trace.endSection(); + } + + private float lerp(float start, float end, float fraction) { + return start * (1 - fraction) + end * fraction; + } + + private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction, + @NonNull RectF out) { + out.set( + start.left * (1 - fraction) + end.left * fraction, + start.top * (1 - fraction) + end.top * fraction, + start.right * (1 - fraction) + end.right * fraction, + start.bottom * (1 - fraction) + end.bottom * fraction); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 9bb383f0b61a..0448d94669ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -57,11 +57,6 @@ public class PipScheduler { @Nullable private SurfaceControl mPinnedTaskLeash; - // the leash of the original task of the PiP activity; - // used to synchronize app drawings in the multi-activity case - @Nullable - private SurfaceControl mOriginalTaskLeash; - /** * A temporary broadcast receiver to initiate exit PiP via expand. * This will later be modified to be triggered by the PiP menu. @@ -95,10 +90,6 @@ public class PipScheduler { mPinnedTaskLeash = pinnedTaskLeash; } - void setOriginalTaskLeash(SurfaceControl originalTaskLeash) { - mOriginalTaskLeash = originalTaskLeash; - } - void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) { mPipTaskToken = pipTaskToken; } @@ -133,6 +124,5 @@ public class PipScheduler { void onExitPip() { mPipTaskToken = null; mPinnedTaskLeash = null; - mOriginalTaskLeash = null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 7d3bd658d126..6200ea583a48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -16,7 +16,6 @@ package com.android.wm.shell.pip2.phone; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.WindowManager.TRANSIT_OPEN; @@ -50,7 +49,7 @@ import com.android.wm.shell.transition.Transitions; public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); - private PipScheduler mPipScheduler; + private final PipScheduler mPipScheduler; @Nullable private WindowContainerToken mPipTaskToken; @Nullable @@ -168,14 +167,9 @@ public class PipTransition extends PipTransitionController { } mPipTaskToken = pipChange.getContainer(); - // cache the PiP task token and the relevant leashes + // cache the PiP task token and leash mPipScheduler.setPipTaskToken(mPipTaskToken); mPipScheduler.setPinnedTaskLeash(pipChange.getLeash()); - // check if we entered PiP from a multi-activity task and set the original task leash - final int lastParentTaskId = pipChange.getTaskInfo().lastParentTaskIdBeforePip; - final boolean isSingleActivity = lastParentTaskId == INVALID_TASK_ID; - mPipScheduler.setOriginalTaskLeash(isSingleActivity ? null : - findChangeByTaskId(info, lastParentTaskId).getLeash()); startTransaction.apply(); finishCallback.onTransitionFinished(null); @@ -201,17 +195,6 @@ public class PipTransition extends PipTransitionController { return null; } - @Nullable - private TransitionInfo.Change findChangeByTaskId(TransitionInfo info, int taskId) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getTaskInfo() != null - && change.getTaskInfo().taskId == taskId) { - return change; - } - } - return null; - } - private void onExitPip() { mPipTaskToken = null; mPipScheduler.onExitPip(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 41ec33c9c762..b98762d5e104 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -654,12 +654,27 @@ public class Transitions implements RemoteCallable<Transitions>, info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", info.getDebugId(), transitionToken, info); - final int activeIdx = findByToken(mPendingTransitions, transitionToken); + int activeIdx = findByToken(mPendingTransitions, transitionToken); if (activeIdx < 0) { - throw new IllegalStateException("Got transitionReady for non-pending transition " + final ActiveTransition existing = getKnownTransition(transitionToken); + if (existing != null) { + Log.e(TAG, "Got duplicate transitionReady for " + transitionToken); + // The transition is already somewhere else in the pipeline, so just return here. + t.apply(); + existing.mFinishT.merge(finishT); + return; + } + // This usually means the system is in a bad state and may not recover; however, + // there's an incentive to propagate bad states rather than crash, so we're kinda + // required to do the same thing I guess. + Log.wtf(TAG, "Got transitionReady for non-pending transition " + transitionToken + ". expecting one of " + Arrays.toString(mPendingTransitions.stream().map( activeTransition -> activeTransition.mToken).toArray())); + final ActiveTransition fallback = new ActiveTransition(); + fallback.mToken = transitionToken; + mPendingTransitions.add(fallback); + activeIdx = mPendingTransitions.size() - 1; } // Move from pending to ready final ActiveTransition active = mPendingTransitions.remove(activeIdx); @@ -1050,34 +1065,43 @@ public class Transitions implements RemoteCallable<Transitions>, processReadyQueue(track); } - private boolean isTransitionKnown(IBinder token) { + /** + * Checks to see if the transition specified by `token` is already known. If so, it will be + * returned. + */ + @Nullable + private ActiveTransition getKnownTransition(IBinder token) { for (int i = 0; i < mPendingTransitions.size(); ++i) { - if (mPendingTransitions.get(i).mToken == token) return true; + final ActiveTransition active = mPendingTransitions.get(i); + if (active.mToken == token) return active; } for (int i = 0; i < mReadyDuringSync.size(); ++i) { - if (mReadyDuringSync.get(i).mToken == token) return true; + final ActiveTransition active = mReadyDuringSync.get(i); + if (active.mToken == token) return active; } for (int t = 0; t < mTracks.size(); ++t) { final Track tr = mTracks.get(t); for (int i = 0; i < tr.mReadyTransitions.size(); ++i) { - if (tr.mReadyTransitions.get(i).mToken == token) return true; + final ActiveTransition active = tr.mReadyTransitions.get(i); + if (active.mToken == token) return active; } final ActiveTransition active = tr.mActiveTransition; if (active == null) continue; - if (active.mToken == token) return true; + if (active.mToken == token) return active; if (active.mMerged == null) continue; for (int m = 0; m < active.mMerged.size(); ++m) { - if (active.mMerged.get(m).mToken == token) return true; + final ActiveTransition merged = active.mMerged.get(m); + if (merged.mToken == token) return merged; } } - return false; + return null; } void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", request.getDebugId(), transitionToken, request); - if (isTransitionKnown(transitionToken)) { + if (getKnownTransition(transitionToken) != null) { throw new RuntimeException("Transition already started " + transitionToken); } final ActiveTransition active = new ActiveTransition(); @@ -1161,7 +1185,7 @@ public class Transitions implements RemoteCallable<Transitions>, */ private void finishForSync(ActiveTransition reason, int trackIdx, @Nullable ActiveTransition forceFinish) { - if (!isTransitionKnown(reason.mToken)) { + if (getKnownTransition(reason.mToken) == null) { Log.d(TAG, "finishForSleep: already played sync transition " + reason); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 3add6f4175bc..dd6ca8da56eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -536,6 +536,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (mGestureDetector.onTouchEvent(e)) { return true; } + if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) { + // If a motion event is cancelled, reset mShouldClick so a click is not accidentally + // performed. + mShouldClick = false; + } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto index 406ada97a07d..5a017ad21044 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/trace_config/trace_config.textproto @@ -63,11 +63,7 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker" atrace_apps: "com.android.wm.shell.flicker.other" - atrace_apps: "com.android.wm.shell.flicker.bubbles" - atrace_apps: "com.android.wm.shell.flicker.pip" - atrace_apps: "com.android.wm.shell.flicker.splitscreen" atrace_apps: "com.google.android.apps.nexuslauncher" } } diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto index 406ada97a07d..15998311e43a 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/bubble/trace_config/trace_config.textproto @@ -63,11 +63,7 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker" - atrace_apps: "com.android.wm.shell.flicker.other" atrace_apps: "com.android.wm.shell.flicker.bubbles" - atrace_apps: "com.android.wm.shell.flicker.pip" - atrace_apps: "com.android.wm.shell.flicker.splitscreen" atrace_apps: "com.google.android.apps.nexuslauncher" } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto index 406ada97a07d..fc15ff9b9af8 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/pip/trace_config/trace_config.textproto @@ -63,11 +63,7 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker" - atrace_apps: "com.android.wm.shell.flicker.other" - atrace_apps: "com.android.wm.shell.flicker.bubbles" atrace_apps: "com.android.wm.shell.flicker.pip" - atrace_apps: "com.android.wm.shell.flicker.splitscreen" atrace_apps: "com.google.android.apps.nexuslauncher" } } diff --git a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto index 406ada97a07d..9f2e49755fec 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/service/trace_config/trace_config.textproto @@ -63,11 +63,7 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker" - atrace_apps: "com.android.wm.shell.flicker.other" - atrace_apps: "com.android.wm.shell.flicker.bubbles" - atrace_apps: "com.android.wm.shell.flicker.pip" - atrace_apps: "com.android.wm.shell.flicker.splitscreen" + atrace_apps: "com.android.wm.shell.flicker.service" atrace_apps: "com.google.android.apps.nexuslauncher" } } diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto index 406ada97a07d..b55f4ecdb6a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/trace_config/trace_config.textproto @@ -63,10 +63,6 @@ data_sources: { atrace_categories: "sched_process_exit" atrace_apps: "com.android.server.wm.flicker.testapp" atrace_apps: "com.android.systemui" - atrace_apps: "com.android.wm.shell.flicker" - atrace_apps: "com.android.wm.shell.flicker.other" - atrace_apps: "com.android.wm.shell.flicker.bubbles" - atrace_apps: "com.android.wm.shell.flicker.pip" atrace_apps: "com.android.wm.shell.flicker.splitscreen" atrace_apps: "com.google.android.apps.nexuslauncher" } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 4e2b7f6d16b2..800f9e4e5371 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -66,6 +66,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -283,7 +284,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .round(any(), any(), anyBoolean()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) - .scale(any(), any(), any(), any(), anyFloat()); + .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .alpha(any(), any(), anyFloat()); doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any()); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index da728f90e8e0..79a735786c38 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -553,6 +553,7 @@ cc_defaults { "utils/Blur.cpp", "utils/Color.cpp", "utils/LinearAllocator.cpp", + "utils/TypefaceUtils.cpp", "utils/VectorDrawableUtils.cpp", "AnimationContext.cpp", "Animator.cpp", diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 34cb4aef70d9..f4ee36ec66d1 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -30,6 +30,7 @@ #include <minikin/MinikinExtent.h> #include <minikin/MinikinPaint.h> #include <minikin/MinikinRect.h> +#include <utils/TypefaceUtils.h> namespace android { @@ -142,7 +143,7 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( skVariation[i].value = SkFloatToScalar(variations[i].value); } args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); - sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args)); return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize, diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index caffdfc907f7..ef4dce57bf46 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -17,19 +17,19 @@ #ifndef ANDROID_GRAPHICS_PAINT_H_ #define ANDROID_GRAPHICS_PAINT_H_ -#include "Typeface.h" - -#include <cutils/compiler.h> - #include <SkFont.h> #include <SkPaint.h> #include <SkSamplingOptions.h> -#include <string> - -#include <minikin/FontFamily.h> +#include <cutils/compiler.h> #include <minikin/FamilyVariant.h> +#include <minikin/FontFamily.h> +#include <minikin/FontFeature.h> #include <minikin/Hyphenator.h> +#include <string> + +#include "Typeface.h" + namespace android { class BlurDrawLooper; @@ -82,11 +82,15 @@ public: float getWordSpacing() const { return mWordSpacing; } - void setFontFeatureSettings(const std::string& fontFeatureSettings) { - mFontFeatureSettings = fontFeatureSettings; + void setFontFeatureSettings(std::string_view fontFeatures) { + mFontFeatureSettings = minikin::FontFeature::parse(fontFeatures); } - std::string getFontFeatureSettings() const { return mFontFeatureSettings; } + void resetFontFeatures() { mFontFeatureSettings.clear(); } + + const std::vector<minikin::FontFeature>& getFontFeatureSettings() const { + return mFontFeatureSettings; + } void setMinikinLocaleListId(uint32_t minikinLocaleListId) { mMinikinLocaleListId = minikinLocaleListId; @@ -170,7 +174,7 @@ private: float mLetterSpacing = 0; float mWordSpacing = 0; - std::string mFontFeatureSettings; + std::vector<minikin::FontFeature> mFontFeatureSettings; uint32_t mMinikinLocaleListId; std::optional<minikin::FamilyVariant> mFamilyVariant; uint32_t mHyphenEdit = 0; diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index 0c3af61fc089..e6d790f56d0f 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -31,6 +31,7 @@ #include <minikin/FontFamily.h> #include <minikin/LocaleList.h> #include <ui/FatVector.h> +#include <utils/TypefaceUtils.h> #include <memory> @@ -125,7 +126,7 @@ static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, in args.setCollectionIndex(ttcIndex); args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); - sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args)); if (face == NULL) { ALOGE("addFont failed to create font, invalid request"); diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 8c71d6fc7860..d84b73d1a1ca 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -33,6 +33,7 @@ #include <cassert> #include <cstring> #include <memory> +#include <string_view> #include <vector> #include "ColorFilter.h" @@ -690,10 +691,11 @@ namespace PaintGlue { jstring settings) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); if (!settings) { - paint->setFontFeatureSettings(std::string()); + paint->resetFontFeatures(); } else { ScopedUtfChars settingsChars(env, settings); - paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); + paint->setFontFeatureSettings( + std::string_view(settingsChars.c_str(), settingsChars.size())); } } diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 2ec94c954fe9..f405abaaf5b4 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -37,6 +37,7 @@ #include <minikin/LocaleList.h> #include <minikin/SystemFonts.h> #include <ui/FatVector.h> +#include <utils/TypefaceUtils.h> #include <memory> @@ -459,7 +460,7 @@ std::shared_ptr<minikin::MinikinFont> createMinikinFontSkia( args.setCollectionIndex(ttcIndex); args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); - sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args)); if (face == nullptr) { return nullptr; diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 499afa039d1f..c71c4d243a8b 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -29,6 +29,7 @@ #include "hwui/MinikinSkia.h" #include "hwui/Typeface.h" +#include "utils/TypefaceUtils.h" using namespace android; @@ -56,7 +57,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkData> skData = SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size)); std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData)); - sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr<minikin::MinikinFont> font = diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp index db2be20936fb..c70a30477ecf 100644 --- a/libs/hwui/tests/unit/UnderlineTest.cpp +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -36,6 +36,7 @@ #include "hwui/MinikinUtils.h" #include "hwui/Paint.h" #include "hwui/Typeface.h" +#include "utils/TypefaceUtils.h" using namespace android; @@ -66,7 +67,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkData> skData = SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size)); std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData)); - sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr<minikin::MinikinFont> font = diff --git a/libs/hwui/utils/TypefaceUtils.cpp b/libs/hwui/utils/TypefaceUtils.cpp new file mode 100644 index 000000000000..a30b9257cd28 --- /dev/null +++ b/libs/hwui/utils/TypefaceUtils.cpp @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include <utils/TypefaceUtils.h> + +#include "include/ports/SkFontMgr_empty.h" + +namespace android { + +sk_sp<SkFontMgr> FreeTypeFontMgr() { + static sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty(); + return mgr; +} + +} // namespace android diff --git a/libs/hwui/utils/TypefaceUtils.h b/libs/hwui/utils/TypefaceUtils.h new file mode 100644 index 000000000000..c0adeaea3c6c --- /dev/null +++ b/libs/hwui/utils/TypefaceUtils.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#pragma once + +#include "SkFontMgr.h" +#include "SkRefCnt.h" + +namespace android { + +// Return an SkFontMgr which is capable of turning bytes into a SkTypeface using Freetype. +// There are no other fonts inside this SkFontMgr (e.g. no system fonts). +sk_sp<SkFontMgr> FreeTypeFontMgr(); + +} // namespace android
\ No newline at end of file diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5d211f460cc5..de842e68b118 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -19,8 +19,8 @@ package android.media; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; - import static android.media.audio.Flags.autoPublicVolumeApiHardening; +import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; import android.Manifest; @@ -2942,6 +2942,33 @@ public class AudioManager { } //==================================================================== + // Loudness management + private final Object mLoudnessCodecLock = new Object(); + + @GuardedBy("mLoudnessCodecLock") + private LoudnessCodecDispatcher mLoudnessCodecDispatcher = null; + + /** + * Creates a new instance of {@link LoudnessCodecConfigurator}. + * @return the {@link LoudnessCodecConfigurator} instance + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public @NonNull LoudnessCodecConfigurator createLoudnessCodecConfigurator() { + LoudnessCodecConfigurator configurator; + synchronized (mLoudnessCodecLock) { + // initialize lazily + if (mLoudnessCodecDispatcher == null) { + mLoudnessCodecDispatcher = new LoudnessCodecDispatcher(this); + } + configurator = mLoudnessCodecDispatcher.createLoudnessCodecConfigurator(); + } + return configurator; + } + + //==================================================================== // Bluetooth SCO control /** * Sticky broadcast intent action indicating that the Bluetooth SCO audio diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 12ddf9b34f10..11e3a088b5e2 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -37,6 +37,7 @@ import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.ICommunicationDeviceDispatcher; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.IDevicesForAttributesCallback; +import android.media.ILoudnessCodecUpdatesDispatcher; import android.media.IMuteAwaitConnectionCallback; import android.media.IPlaybackConfigDispatcher; import android.media.IPreferredMixerAttributesDispatcher; @@ -51,6 +52,7 @@ import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerOutputCallback; import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; +import android.media.LoudnessCodecFormat; import android.media.PlayerBase; import android.media.VolumeInfo; import android.media.VolumePolicy; @@ -728,4 +730,20 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_ROUTING") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") boolean isBluetoothVariableLatencyEnabled(); + + void registerLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); + + void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); + + oneway void startLoudnessCodecUpdates(in int piid); + + oneway void stopLoudnessCodecUpdates(in int piid); + + oneway void addLoudnesssCodecFormat(in int piid, in LoudnessCodecFormat format); + + oneway void addLoudnesssCodecFormatList(in int piid, in List<LoudnessCodecFormat> format); + + oneway void removeLoudnessCodecFormat(in int piid, in LoudnessCodecFormat format); + + PersistableBundle getLoudnessParams(in int piid, in LoudnessCodecFormat format); } diff --git a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl new file mode 100644 index 000000000000..16eaaea38b7f --- /dev/null +++ b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl @@ -0,0 +1,31 @@ +/* + * 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.media; + +import android.os.PersistableBundle; + +/** + * Interface which provides updates for the clients about MediaCodec loudness + * parameter changes. + * + * {@hide} + */ +oneway interface ILoudnessCodecUpdatesDispatcher { + + void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params); + +}
\ No newline at end of file diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java new file mode 100644 index 000000000000..409abc211cb6 --- /dev/null +++ b/media/java/android/media/LoudnessCodecConfigurator.java @@ -0,0 +1,225 @@ +/* + * 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.media; + +import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Class for getting recommended loudness parameter updates for audio decoders, according to the + * encoded format and current audio routing. Those updates can be automatically applied to the + * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management + * updates are defined by the CTA-2075 standard. + * <p>A new object should be instantiated for each {@link AudioTrack} with the help + * of {@link AudioManager#createLoudnessCodecConfigurator()}. + * + * TODO: remove hide once API is final + * @hide + */ +@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) +public class LoudnessCodecConfigurator { + private static final String TAG = "LoudnessCodecConfigurator"; + + /** + * Listener used for receiving asynchronous loudness metadata updates. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public interface OnLoudnessCodecUpdateListener { + /** + * Contains the MediaCodec key/values that can be set directly to + * configure the loudness of the handle's corresponding decoder (see + * {@link MediaCodec#setParameters(Bundle)}). + * + * @param mediaCodec the mediaCodec that will receive the new parameters + * @param codecValues contains loudness key/value pairs that can be set + * directly on the mediaCodec. The listener can modify + * these values with their own edits which will be + * returned for the mediaCodec configuration + * @return a Bundle which contains the original computed codecValues + * aggregated with user edits. The platform will configure the associated + * MediaCodecs with the returned Bundle params. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + @NonNull + default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec, + @NonNull Bundle codecValues) { + return codecValues; + } + } + + @NonNull private final LoudnessCodecDispatcher mLcDispatcher; + + private AudioTrack mAudioTrack; + + private final List<MediaCodec> mMediaCodecs = new ArrayList<>(); + + /** @hide */ + protected LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher) { + mLcDispatcher = Objects.requireNonNull(lcDispatcher); + } + + + /** + * Starts receiving asynchronous loudness updates and registers the listener for + * receiving {@link MediaCodec} loudness parameter updates. + * <p>This method should be called before {@link #startLoudnessCodecUpdates()} or + * after {@link #stopLoudnessCodecUpdates()}. + * + * @param executor {@link Executor} to handle the callbacks + * @param listener used to receive updates + * + * @return {@code true} if there is at least one {@link MediaCodec} and + * {@link AudioTrack} set and the user can expect receiving updates. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public boolean startLoudnessCodecUpdates(@NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + Objects.requireNonNull(executor, + "Executor must not be null"); + Objects.requireNonNull(listener, + "OnLoudnessCodecUpdateListener must not be null"); + mLcDispatcher.addLoudnessCodecListener(this, executor, listener); + + return checkStartLoudnessConfigurator(); + } + + /** + * Starts receiving asynchronous loudness updates. + * <p>The registered MediaCodecs will be updated automatically without any client + * callbacks. + * + * @return {@code true} if there is at least one MediaCodec and AudioTrack set + * (see {@link #setAudioTrack(AudioTrack)}, {@link #addMediaCodec(MediaCodec)}) + * and the user can expect receiving updates. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public boolean startLoudnessCodecUpdates() { + mLcDispatcher.addLoudnessCodecListener(this, + Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); + return checkStartLoudnessConfigurator(); + } + + /** + * Stops receiving asynchronous loudness updates. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public void stopLoudnessCodecUpdates() { + mLcDispatcher.removeLoudnessCodecListener(this); + } + + /** + * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} + * which is registered through {@link #setAudioTrack(AudioTrack)}. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public void addMediaCodec(@NonNull MediaCodec mediaCodec) { + mMediaCodecs.add(Objects.requireNonNull(mediaCodec, + "MediaCodec for addMediaCodec must not be null")); + } + + /** + * Removes the {@link MediaCodec} from receiving loudness updates. + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public void removeMediaCodec(@NonNull MediaCodec mediaCodec) { + mMediaCodecs.remove(Objects.requireNonNull(mediaCodec, + "MediaCodec for removeMediaCodec must not be null")); + } + + /** + * Sets the {@link AudioTrack} that can receive audio data from the added + * {@link MediaCodec}'s. The {@link AudioTrack} is used to determine the devices + * on which the streaming will take place and hence will directly influence the + * loudness params. + * <p>Should be called before starting the loudness updates + * (see {@link #startLoudnessCodecUpdates()}, + * {@link #startLoudnessCodecUpdates(Executor, OnLoudnessCodecUpdateListener)}) + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + public void setAudioTrack(@NonNull AudioTrack audioTrack) { + mAudioTrack = Objects.requireNonNull(audioTrack, + "AudioTrack for setAudioTrack must not be null"); + } + + /** + * Gets synchronous loudness updates when no listener is required and at least one + * {@link MediaCodec} which streams to a registered {@link AudioTrack} is set. + * Otherwise, an empty {@link Bundle} will be returned. + * + * @return the {@link Bundle} containing the current loudness parameters. Caller is + * responsible to update the {@link MediaCodec} + * + * TODO: remove hide once API is final + * @hide + */ + @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) + @NonNull + public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) { + // TODO: implement synchronous loudness params updates + return new Bundle(); + } + + private boolean checkStartLoudnessConfigurator() { + if (mAudioTrack == null) { + Log.w(TAG, "Cannot start loudness configurator without an AudioTrack"); + return false; + } + + if (mMediaCodecs.isEmpty()) { + Log.w(TAG, "Cannot start loudness configurator without at least one MediaCodec"); + return false; + } + + return true; + } +} diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java new file mode 100644 index 000000000000..fc5c354b98f5 --- /dev/null +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -0,0 +1,109 @@ +/* + * 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.media; + +import android.annotation.CallbackExecutor; +import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener; +import android.os.PersistableBundle; +import android.os.RemoteException; + +import androidx.annotation.NonNull; + +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Class used to handle the loudness related communication with the audio service. + * @hide + */ +public class LoudnessCodecDispatcher { + private final class LoudnessCodecUpdatesDispatcherStub + extends ILoudnessCodecUpdatesDispatcher.Stub + implements CallbackUtil.DispatcherStub { + @Override + public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) { + mLoudnessListenerMgr.callListeners(listener -> + mConfiguratorListener.computeIfPresent(listener, (l, c) -> { + // TODO: send the bundle for the user to update + return c; + })); + } + + @Override + public void register(boolean register) { + try { + if (register) { + mAm.getService().registerLoudnessCodecUpdatesDispatcher(this); + } else { + mAm.getService().unregisterLoudnessCodecUpdatesDispatcher(this); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener> + mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); + + @NonNull private final LoudnessCodecUpdatesDispatcherStub mLoudnessCodecStub; + + private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> + mConfiguratorListener = new HashMap<>(); + + @NonNull private final AudioManager mAm; + + protected LoudnessCodecDispatcher(@NonNull AudioManager am) { + mAm = Objects.requireNonNull(am); + mLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub(); + } + + /** @hide */ + public LoudnessCodecConfigurator createLoudnessCodecConfigurator() { + return new LoudnessCodecConfigurator(this); + } + + /** @hide */ + public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator, + @NonNull @CallbackExecutor Executor executor, + @NonNull OnLoudnessCodecUpdateListener listener) { + Objects.requireNonNull(configurator); + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + + mConfiguratorListener.put(listener, configurator); + mLoudnessListenerMgr.addListener( + executor, listener, "addLoudnessCodecListener", () -> mLoudnessCodecStub); + } + + /** @hide */ + public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) { + Objects.requireNonNull(configurator); + + for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e : + mConfiguratorListener.entrySet()) { + if (e.getValue() == configurator) { + final OnLoudnessCodecUpdateListener listener = e.getKey(); + mConfiguratorListener.remove(listener); + mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener"); + break; + } + } + } +} diff --git a/media/java/android/media/LoudnessCodecFormat.aidl b/media/java/android/media/LoudnessCodecFormat.aidl new file mode 100644 index 000000000000..75c906060d43 --- /dev/null +++ b/media/java/android/media/LoudnessCodecFormat.aidl @@ -0,0 +1,30 @@ +/* + * 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.media; + + +/** + * Loudness format which specifies the input attributes used for measuring + * the parameters required to perform loudness alignment as specified by the + * CTA2075 standard. + * + * {@hide} + */ +parcelable LoudnessCodecFormat { + String metadataType; + boolean isDownmixing; +}
\ No newline at end of file diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index 5509782e2988..f52297450e85 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -148,6 +148,7 @@ public class MediaMetrics { createKey("headTrackerEnabled", String.class); // spatializer public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume + public static final Key<Integer> OLD_INDEX = createKey("oldIndex", Integer.class); // volume public static final Key<Integer> INPUT_PORT_COUNT = createKey("inputPortCount", Integer.class); // MIDI // Either "true" or "false" diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 9f2a9ac4798d..fea6c5f95358 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -336,13 +336,6 @@ LIBANDROID { APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream - APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream - AWorkDuration_create; # introduced=VanillaIceCream - AWorkDuration_release; # introduced=VanillaIceCream - AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream - AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream local: *; }; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c4c81284780e..c25df6e08fd0 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -18,14 +18,12 @@ #include <aidl/android/hardware/power/SessionHint.h> #include <aidl/android/hardware/power/SessionMode.h> -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> #include <binder/Binder.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> -#include <inttypes.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> @@ -77,13 +75,10 @@ public: int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); - int reportActualWorkDuration(AWorkDuration* workDuration); private: friend struct APerformanceHintManager; - int reportActualWorkDurationInternal(WorkDuration* workDuration); - sp<IHintManager> mHintManager; sp<IHintSession> mHintSession; // HAL preferred update rate @@ -97,7 +92,8 @@ private: // Last hint reported from sendHint indexed by hint value std::vector<int64_t> mLastHintSentTimestamp; // Cached samples - std::vector<WorkDuration> mActualWorkDurations; + std::vector<int64_t> mActualDurationsNanos; + std::vector<int64_t> mTimestampsNanos; }; static IHintManager* gIHintManagerForTesting = nullptr; @@ -199,7 +195,8 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano * Most of the workload is target_duration dependent, so now clear the cached samples * as they are most likely obsolete. */ - mActualWorkDurations.clear(); + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); mFirstTargetMetTimestamp = 0; mLastTargetMetTimestamp = 0; return 0; @@ -210,10 +207,43 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); return EINVAL; } + int64_t now = elapsedRealtimeNano(); + mActualDurationsNanos.push_back(actualDurationNanos); + mTimestampsNanos.push_back(now); - WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0); + if (actualDurationNanos >= mTargetDurationNanos) { + // Reset timestamps if we are equal or over the target. + mFirstTargetMetTimestamp = 0; + } else { + // Set mFirstTargetMetTimestamp for first time meeting target. + if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || + (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { + mFirstTargetMetTimestamp = now; + } + /** + * Rate limit the change if the update is over mPreferredRateNanos since first + * meeting target and less than mPreferredRateNanos since last meeting target. + */ + if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && + now - mLastTargetMetTimestamp <= mPreferredRateNanos) { + return 0; + } + mLastTargetMetTimestamp = now; + } - return reportActualWorkDurationInternal(&workDuration); + binder::Status ret = + mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos); + if (!ret.isOk()) { + ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, + ret.exceptionMessage().c_str()); + mFirstTargetMetTimestamp = 0; + mLastTargetMetTimestamp = 0; + return EPIPE; + } + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); + + return 0; } int APerformanceHintSession::sendHint(SessionHint hint) { @@ -292,67 +322,6 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) { return OK; } -int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) { - WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration); - if (workDuration->workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualTotalDurationNanos <= 0) { - ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualCpuDurationNanos <= 0) { - ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualGpuDurationNanos < 0) { - ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__); - return EINVAL; - } - - return reportActualWorkDurationInternal(workDuration); -} - -int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) { - int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos; - int64_t now = uptimeNanos(); - workDuration->timestampNanos = now; - mActualWorkDurations.push_back(std::move(*workDuration)); - - if (actualTotalDurationNanos >= mTargetDurationNanos) { - // Reset timestamps if we are equal or over the target. - mFirstTargetMetTimestamp = 0; - } else { - // Set mFirstTargetMetTimestamp for first time meeting target. - if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || - (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { - mFirstTargetMetTimestamp = now; - } - /** - * Rate limit the change if the update is over mPreferredRateNanos since first - * meeting target and less than mPreferredRateNanos since last meeting target. - */ - if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && - now - mLastTargetMetTimestamp <= mPreferredRateNanos) { - return 0; - } - mLastTargetMetTimestamp = now; - } - - binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations); - if (!ret.isOk()) { - ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, - ret.exceptionMessage().c_str()); - mFirstTargetMetTimestamp = 0; - mLastTargetMetTimestamp = 0; - return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE; - } - mActualWorkDurations.clear(); - - return 0; -} - // ===================================== C API APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); @@ -407,64 +376,6 @@ int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, return session->setPreferPowerEfficiency(enabled); } -int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, - AWorkDuration* workDuration) { - if (session == nullptr || workDuration == nullptr) { - ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration); - return EINVAL; - } - return session->reportActualWorkDuration(workDuration); -} - -AWorkDuration* AWorkDuration_create() { - WorkDuration* workDuration = new WorkDuration(); - return static_cast<AWorkDuration*>(workDuration); -} - -void AWorkDuration_release(AWorkDuration* aWorkDuration) { - if (aWorkDuration == nullptr) { - ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__); - } - delete aWorkDuration; -} - -void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration, - int64_t workPeriodStartTimestampNanos) { - if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos = - workPeriodStartTimestampNanos; -} - -void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualTotalDurationNanos) { - if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualTotalDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos; -} - -void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualCpuDurationNanos) { - if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualCpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; -} - -void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualGpuDurationNanos) { - if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualGpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos; -} - void APerformanceHint_setIHintManagerForTesting(void* iManager) { delete gHintManagerForTesting; gHintManagerForTesting = nullptr; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 4553b4919d2d..22d33b139ccf 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,7 +16,6 @@ #define LOG_TAG "PerformanceHintNativeTest" -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> @@ -61,8 +60,6 @@ public: MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override)); MOCK_METHOD(Status, close, (), (override)); MOCK_METHOD(IBinder*, onAsBinder, (), (override)); - MOCK_METHOD(Status, reportActualWorkDuration2, - (const ::std::vector<android::os::WorkDuration>& workDurations), (override)); }; class PerformanceHintTest : public Test { @@ -123,7 +120,6 @@ TEST_F(PerformanceHintTest, TestSession) { std::vector<int64_t> actualDurations; actualDurations.push_back(20); EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1)); - EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1)); result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); EXPECT_EQ(0, result); @@ -242,125 +238,4 @@ TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) { APerformanceHintSession* session = APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); ASSERT_TRUE(session); -} - -MATCHER_P(WorkDurationEq, expected, "") { - if (arg.size() != expected.size()) { - *result_listener << "WorkDuration vectors are different sizes. Expected: " - << expected.size() << ", Actual: " << arg.size(); - return false; - } - for (int i = 0; i < expected.size(); ++i) { - android::os::WorkDuration expectedWorkDuration = expected[i]; - android::os::WorkDuration actualWorkDuration = arg[i]; - if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) { - *result_listener << "WorkDuration at [" << i << "] is different: " - << "Expected: " << expectedWorkDuration - << ", Actual: " << actualWorkDuration; - return false; - } - } - return true; -} - -TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { - APerformanceHintManager* manager = createManager(); - - std::vector<int32_t> tids; - tids.push_back(1); - tids.push_back(2); - int64_t targetDuration = 56789L; - - StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>(); - sp<IHintSession> session_sp(iSession); - - EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _)) - .Times(Exactly(1)) - .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status()))); - - APerformanceHintSession* session = - APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); - ASSERT_TRUE(session); - - int64_t targetDurationNanos = 10; - EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); - int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); - EXPECT_EQ(0, result); - - usleep(2); // Sleep for longer than preferredUpdateRateNanos. - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(0, result); - } - - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(-1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, -20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, -13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, -8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - - EXPECT_CALL(*iSession, close()).Times(Exactly(1)); - APerformanceHint_closeSession(session); -} - -TEST_F(PerformanceHintTest, TestAWorkDuration) { - AWorkDuration* aWorkDuration = AWorkDuration_create(); - ASSERT_NE(aWorkDuration, nullptr); - - AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1); - AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20); - AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13); - AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8); - AWorkDuration_release(aWorkDuration); -} +}
\ No newline at end of file diff --git a/nfc/Android.bp b/nfc/Android.bp new file mode 100644 index 000000000000..bf9f47ceb0a7 --- /dev/null +++ b/nfc/Android.bp @@ -0,0 +1,51 @@ +package { + // 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" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-nfc-non-updatable-sources", + path: "java", + srcs: [], +} + +filegroup { + name: "framework-nfc-updatable-sources", + path: "java", + srcs: [ + "java/**/*.java", + "java/**/*.aidl", + ], + exclude_srcs: [ + ":framework-nfc-non-updatable-sources", + ], +} + +java_sdk_library { + name: "framework-nfc", + libs: [ + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + ], + srcs: [ + ":framework-nfc-updatable-sources", + ], + defaults: ["framework-non-updatable-unbundled-defaults"], + permitted_packages: [ + "android.nfc", + "com.android.nfc", + ], + hidden_api_packages: [ + "com.android.nfc", + ], + aidl: { + include_dirs: [ + // TODO (b/303286040): Remove these when we change to |framework-module-defaults| + "frameworks/base/nfc/java", + "frameworks/base/core/java", + ], + }, +} diff --git a/nfc/OWNERS b/nfc/OWNERS new file mode 100644 index 000000000000..35e9713f5715 --- /dev/null +++ b/nfc/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 48448 +include platform/packages/apps/Nfc:/OWNERS diff --git a/nfc/TEST_MAPPING b/nfc/TEST_MAPPING new file mode 100644 index 000000000000..5b5ea3790010 --- /dev/null +++ b/nfc/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "NfcManagerTests" + }, + { + "name": "CtsNfcTestCases" + } + ] +} diff --git a/nfc/api/current.txt b/nfc/api/current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/module-lib-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/module-lib-removed.txt b/nfc/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/test-current.txt b/nfc/api/test-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/nfc/api/test-removed.txt b/nfc/api/test-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/nfc/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/core/java/android/os/WorkDuration.aidl b/nfc/java/android/nfc/Placeholder.java index 0f61204d72c4..3509644ac106 100644 --- a/core/java/android/os/WorkDuration.aidl +++ b/nfc/java/android/nfc/Placeholder.java @@ -14,6 +14,14 @@ * limitations under the License. */ -package android.os; +package android.nfc; -parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file +/** + * Placeholder class so new framework-nfc module isn't empty, will be removed once module is + * populated. + * + * @hide + * + */ +public class Placeholder { +} diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index b845c2b6cc8b..6e47689d585c 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -43,6 +43,14 @@ </intent-filter> </receiver> + <receiver android:name="v2.model.TemporaryFileManager" + android:exported="false" + android:enabled="false"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + </receiver> + <activity android:name=".v2.ui.InstallLaunch" android:configChanges="orientation|keyboardHidden|screenSize" android:theme="@style/Theme.AlertDialogActivity" @@ -101,6 +109,15 @@ </intent-filter> </receiver> + <receiver android:name=".v2.model.InstallEventReceiver" + android:permission="android.permission.INSTALL_PACKAGES" + android:exported="false" + android:enabled="false"> + <intent-filter android:priority="1"> + <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" /> + </intent-filter> + </receiver> + <activity android:name=".InstallSuccess" android:theme="@style/Theme.AlertDialogActivity.NoAnimation" android:exported="false" /> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java new file mode 100644 index 000000000000..4d2d911f7a8f --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/EventResultPersister.java @@ -0,0 +1,378 @@ +/* + * 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 com.android.packageinstaller.v2.model; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInstaller; +import android.os.AsyncTask; +import android.util.AtomicFile; +import android.util.Log; +import android.util.SparseArray; +import android.util.Xml; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +/** + * Persists results of events and calls back observers when a matching result arrives. + */ +public class EventResultPersister { + + /** + * Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id + */ + public static final int GENERATE_NEW_ID = Integer.MIN_VALUE; + /** + * The extra with the id to set in the intent delivered to + * {@link #onEventReceived(Context, Intent)} + */ + public static final String EXTRA_ID = "EventResultPersister.EXTRA_ID"; + public static final String EXTRA_SERVICE_ID = "EventResultPersister.EXTRA_SERVICE_ID"; + private static final String TAG = EventResultPersister.class.getSimpleName(); + /** + * Persisted state of this object + */ + private final AtomicFile mResultsFile; + + private final Object mLock = new Object(); + + /** + * Currently stored but not yet called back results (install id -> status, status message) + */ + private final SparseArray<EventResult> mResults = new SparseArray<>(); + + /** + * Currently registered, not called back observers (install id -> observer) + */ + private final SparseArray<EventResultObserver> mObservers = new SparseArray<>(); + + /** + * Always increasing counter for install event ids + */ + private int mCounter; + + /** + * If a write that will persist the state is scheduled + */ + private boolean mIsPersistScheduled; + + /** + * If the state was changed while the data was being persisted + */ + private boolean mIsPersistingStateValid; + + /** + * Read persisted state. + * + * @param resultFile The file the results are persisted in + */ + EventResultPersister(@NonNull File resultFile) { + mResultsFile = new AtomicFile(resultFile); + mCounter = GENERATE_NEW_ID + 1; + + try (FileInputStream stream = mResultsFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, StandardCharsets.UTF_8.name()); + + nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("results".equals(tagName)) { + mCounter = readIntAttribute(parser, "counter"); + } else if ("result".equals(tagName)) { + int id = readIntAttribute(parser, "id"); + int status = readIntAttribute(parser, "status"); + int legacyStatus = readIntAttribute(parser, "legacyStatus"); + String statusMessage = readStringAttribute(parser, "statusMessage"); + int serviceId = readIntAttribute(parser, "serviceId"); + + if (mResults.get(id) != null) { + throw new Exception("id " + id + " has two results"); + } + + mResults.put(id, new EventResult(status, legacyStatus, statusMessage, + serviceId)); + } else { + throw new Exception("unexpected tag"); + } + + nextElement(parser); + } + } catch (Exception e) { + mResults.clear(); + writeState(); + } + } + + /** + * Progress parser to the next element. + * + * @param parser The parser to progress + */ + private static void nextElement(@NonNull XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + do { + type = parser.next(); + } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT); + } + + /** + * Read an int attribute from the current element + * + * @param parser The parser to read from + * @param name The attribute name to read + * @return The value of the attribute + */ + private static int readIntAttribute(@NonNull XmlPullParser parser, @NonNull String name) { + return Integer.parseInt(parser.getAttributeValue(null, name)); + } + + /** + * Read an String attribute from the current element + * + * @param parser The parser to read from + * @param name The attribute name to read + * @return The value of the attribute or null if the attribute is not set + */ + private static String readStringAttribute(@NonNull XmlPullParser parser, @NonNull String name) { + return parser.getAttributeValue(null, name); + } + + /** + * @return a new event id. + */ + public int getNewId() throws OutOfIdsException { + synchronized (mLock) { + if (mCounter == Integer.MAX_VALUE) { + throw new OutOfIdsException(); + } + + mCounter++; + writeState(); + + return mCounter - 1; + } + } + + /** + * Add a result. If the result is a pending user action, execute the pending user action + * directly and do not queue a result. + * + * @param context The context the event was received in + * @param intent The intent the activity received + */ + void onEventReceived(@NonNull Context context, @NonNull Intent intent) { + int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0); + + if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { + Intent intentToStart = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + intentToStart.addFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intentToStart); + + return; + } + + int id = intent.getIntExtra(EXTRA_ID, 0); + String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); + int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0); + int serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0); + + EventResultObserver observerToCall = null; + synchronized (mLock) { + int numObservers = mObservers.size(); + for (int i = 0; i < numObservers; i++) { + if (mObservers.keyAt(i) == id) { + observerToCall = mObservers.valueAt(i); + mObservers.removeAt(i); + + break; + } + } + + if (observerToCall != null) { + observerToCall.onResult(status, legacyStatus, statusMessage, serviceId); + } else { + mResults.put(id, new EventResult(status, legacyStatus, statusMessage, serviceId)); + writeState(); + } + } + } + + /** + * Persist current state. The persistence might be delayed. + */ + private void writeState() { + synchronized (mLock) { + mIsPersistingStateValid = false; + + if (!mIsPersistScheduled) { + mIsPersistScheduled = true; + + AsyncTask.execute(() -> { + int counter; + SparseArray<EventResult> results; + + while (true) { + // Take snapshot of state + synchronized (mLock) { + counter = mCounter; + results = mResults.clone(); + mIsPersistingStateValid = true; + } + + try (FileOutputStream stream = mResultsFile.startWrite()) { + try { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(stream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startTag(null, "results"); + serializer.attribute(null, "counter", Integer.toString(counter)); + + int numResults = results.size(); + for (int i = 0; i < numResults; i++) { + serializer.startTag(null, "result"); + serializer.attribute(null, "id", + Integer.toString(results.keyAt(i))); + serializer.attribute(null, "status", + Integer.toString(results.valueAt(i).status)); + serializer.attribute(null, "legacyStatus", + Integer.toString(results.valueAt(i).legacyStatus)); + if (results.valueAt(i).message != null) { + serializer.attribute(null, "statusMessage", + results.valueAt(i).message); + } + serializer.attribute(null, "serviceId", + Integer.toString(results.valueAt(i).serviceId)); + serializer.endTag(null, "result"); + } + + serializer.endTag(null, "results"); + serializer.endDocument(); + + mResultsFile.finishWrite(stream); + } catch (IOException e) { + Log.e(TAG, "error writing results", e); + mResultsFile.failWrite(stream); + mResultsFile.delete(); + } + } catch (IOException e) { + Log.e(TAG, "error writing results", e); + mResultsFile.delete(); + } + + // Check if there was changed state since we persisted. If so, we need to + // persist again. + synchronized (mLock) { + if (mIsPersistingStateValid) { + mIsPersistScheduled = false; + break; + } + } + } + }); + } + } + } + + /** + * Add an observer. If there is already an event for this id, call back inside of this call. + * + * @param id The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one. + * @param observer The observer to call back. + * @return The id for this event + */ + int addObserver(int id, @NonNull EventResultObserver observer) + throws OutOfIdsException { + synchronized (mLock) { + int resultIndex = -1; + + if (id == GENERATE_NEW_ID) { + id = getNewId(); + } else { + resultIndex = mResults.indexOfKey(id); + } + + // Check if we can instantly call back + if (resultIndex >= 0) { + EventResult result = mResults.valueAt(resultIndex); + + observer.onResult(result.status, result.legacyStatus, result.message, + result.serviceId); + mResults.removeAt(resultIndex); + writeState(); + } else { + mObservers.put(id, observer); + } + } + + return id; + } + + /** + * Remove a observer. + * + * @param id The id the observer was added for + */ + void removeObserver(int id) { + synchronized (mLock) { + mObservers.delete(id); + } + } + + /** + * Call back when a result is received. Observer is removed when onResult it called. + */ + public interface EventResultObserver { + + void onResult(int status, int legacyStatus, @Nullable String message, int serviceId); + } + + /** + * The status from an event. + */ + private static class EventResult { + + public final int status; + public final int legacyStatus; + @Nullable + public final String message; + public final int serviceId; + + private EventResult(int status, int legacyStatus, @Nullable String message, int serviceId) { + this.status = status; + this.legacyStatus = legacyStatus; + this.message = message; + this.serviceId = serviceId; + } + } + + public static class OutOfIdsException extends Exception { + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java new file mode 100644 index 000000000000..bcb11c884694 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallEventReceiver.java @@ -0,0 +1,77 @@ +/* + * 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 com.android.packageinstaller.v2.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import androidx.annotation.NonNull; + +/** + * Receives install events and perists them using a {@link EventResultPersister}. + */ +public class InstallEventReceiver extends BroadcastReceiver { + + private static final Object sLock = new Object(); + private static EventResultPersister sReceiver; + + /** + * Get the event receiver persisting the results + * + * @return The event receiver. + */ + @NonNull + private static EventResultPersister getReceiver(@NonNull Context context) { + synchronized (sLock) { + if (sReceiver == null) { + sReceiver = new EventResultPersister( + TemporaryFileManager.getInstallStateFile(context)); + } + } + + return sReceiver; + } + + /** + * Add an observer. If there is already an event for this id, call back inside of this call. + * + * @param context A context of the current app + * @param id The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one. + * @param observer The observer to call back. + * @return The id for this event + */ + static int addObserver(@NonNull Context context, int id, + @NonNull EventResultPersister.EventResultObserver observer) + throws EventResultPersister.OutOfIdsException { + return getReceiver(context).addObserver(id, observer); + } + + /** + * Remove a observer. + * + * @param context A context of the current app + * @param id The id the observer was added for + */ + static void removeObserver(@NonNull Context context, int id) { + getReceiver(context).removeObserver(id); + } + + @Override + public void onReceive(Context context, Intent intent) { + getReceiver(context).onEventReceived(context, intent); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java index 03af95180009..7e7071f5dd24 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java @@ -17,26 +17,42 @@ package com.android.packageinstaller.v2.model; import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery; +import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo; +import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet; +import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo; +import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid; import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner; import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested; import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE; import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR; import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR; +import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE; +import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION; +import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE; import android.Manifest; import android.app.Activity; +import android.app.AppOpsManager; +import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.util.EventLog; @@ -44,22 +60,34 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.EventResultPersister.OutOfIdsException; +import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet; import com.android.packageinstaller.v2.model.installstagedata.InstallAborted; +import com.android.packageinstaller.v2.model.installstagedata.InstallFailed; +import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling; import com.android.packageinstaller.v2.model.installstagedata.InstallReady; import com.android.packageinstaller.v2.model.installstagedata.InstallStage; import com.android.packageinstaller.v2.model.installstagedata.InstallStaging; +import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess; +import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired; +import java.io.File; import java.io.IOException; public class InstallRepository { private static final String SCHEME_PACKAGE = "package"; + private static final String BROADCAST_ACTION = + "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; private static final String TAG = InstallRepository.class.getSimpleName(); private final Context mContext; private final PackageManager mPackageManager; private final PackageInstaller mPackageInstaller; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; + private final AppOpsManager mAppOpsManager; private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>(); + private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>(); private final boolean mLocalLOGV = false; private Intent mIntent; private boolean mIsSessionInstall; @@ -75,6 +103,12 @@ public class InstallRepository { private int mCallingUid; private String mCallingPackage; private SessionStager mSessionStager; + private AppOpRequestInfo mAppOpRequestInfo; + private AppSnippet mAppSnippet; + /** + * PackageInfo of the app being installed on device. + */ + private PackageInfo mNewPackageInfo; public InstallRepository(Context context) { mContext = context; @@ -82,6 +116,7 @@ public class InstallRepository { mPackageInstaller = mPackageManager.getPackageInstaller(); mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); mUserManager = context.getSystemService(UserManager.class); + mAppOpsManager = context.getSystemService(AppOpsManager.class); } /** @@ -124,6 +159,9 @@ public class InstallRepository { final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage); // Uid of the source package, with a preference to uid from ApplicationInfo final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid; + mAppOpRequestInfo = new AppOpRequestInfo( + getPackageNameForUid(mContext, originatingUid, mCallingPackage), + originatingUid, callingAttributionTag); if (mCallingUid == Process.INVALID_UID && sourceInfo == null) { // Caller's identity could not be determined. Abort the install @@ -337,6 +375,466 @@ public class InstallRepository { return params; } + /** + * Processes Install session, file:// or package:// URI to generate data pertaining to user + * confirmation for an install. This method also checks if the source app has the AppOp granted + * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to + * be reused once appOp has been granted + * + * @return <ul> + * <li>InstallAborted </li> + * <ul> + * <li> If install session is invalid (not sealed or resolvedBaseApk path + * is invalid) </li> + * <li> Source app doesn't have visibility to target app </li> + * <li> The APK is invalid </li> + * <li> URI is invalid </li> + * <li> Can't get ApplicationInfo for source app, to request AppOp </li> + * </ul> + * <li> InstallUserActionRequired</li> + * <ul> + * <li> If AppOP is granted and user action is required to proceed + * with install </li> + * <li> If AppOp grant is to be requested from the user</li> + * </ul> + * </ul> + */ + public InstallStage requestUserConfirmation() { + if (mIsTrustedSource) { + if (mLocalLOGV) { + Log.i(TAG, "install allowed"); + } + // Returns InstallUserActionRequired stage if install details could be successfully + // computed, else it returns InstallAborted. + return generateConfirmationSnippet(); + } else { + InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo); + if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) { + // Source app already has appOp granted. + return generateConfirmationSnippet(); + } else { + return unknownSourceStage; + } + } + } + + + private InstallStage generateConfirmationSnippet() { + final Object packageSource; + int pendingUserActionReason = -1; + if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(mIntent.getAction())) { + final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId); + String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; + + if (info == null || !info.isSealed() || resolvedPath == null) { + Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + packageSource = Uri.fromFile(new File(resolvedPath)); + // TODO: Not sure where is this used yet. PIA.java passes it to + // InstallInstalling if not null + // mOriginatingURI = null; + // mReferrerURI = null; + pendingUserActionReason = info.getPendingUserActionReason(); + } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(mIntent.getAction())) { + final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId); + + if (info == null || !info.isPreApprovalRequested()) { + Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + packageSource = info; + // mOriginatingURI = null; + // mReferrerURI = null; + pendingUserActionReason = info.getPendingUserActionReason(); + } else { + // Two possible origins: + // 1. Installation with SCHEME_PACKAGE. + // 2. Installation with "file://" for session created by this app + if (mIntent.getData() != null && mIntent.getData().getScheme().equals(SCHEME_PACKAGE)) { + packageSource = mIntent.getData(); + } else { + SessionInfo stagedSessionInfo = mPackageInstaller.getSessionInfo(mStagedSessionId); + packageSource = Uri.fromFile(new File(stagedSessionInfo.getResolvedBaseApkPath())); + } + // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); + // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER); + pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE; + } + + // if there's nothing to do, quietly slip into the ether + if (packageSource == null) { + Log.w(TAG, "Unspecified source"); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, + PackageManager.INSTALL_FAILED_INVALID_URI)) + .setActivityResultCode(Activity.RESULT_FIRST_USER) + .build(); + } + + return processAppSnippet(packageSource, pendingUserActionReason); + } + + /** + * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install + * session) to set up the installer for this install. + * + * @param source The source of package URI or SessionInfo + * @return {@code true} iff the installer could be set up + */ + private InstallStage processAppSnippet(Object source, int userActionReason) { + if (source instanceof Uri) { + return processPackageUri((Uri) source, userActionReason); + } else if (source instanceof SessionInfo) { + return processSessionInfo((SessionInfo) source, userActionReason); + } + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + + /** + * Parse the Uri and set up the installer for this package. + * + * @param packageUri The URI to parse + * @return {@code true} iff the installer could be set up + */ + private InstallStage processPackageUri(final Uri packageUri, int userActionReason) { + final String scheme = packageUri.getScheme(); + final String packageName = packageUri.getSchemeSpecificPart(); + + if (scheme == null) { + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + + if (mLocalLOGV) { + Log.i(TAG, "processPackageUri(): uri = " + packageUri + ", scheme = " + scheme); + } + + switch (scheme) { + case SCHEME_PACKAGE -> { + for (UserHandle handle : mUserManager.getUserHandles(true)) { + PackageManager pmForUser = mContext.createContextAsUser(handle, 0) + .getPackageManager(); + try { + if (pmForUser.canPackageQuery(mCallingPackage, packageName)) { + mNewPackageInfo = pmForUser.getPackageInfo(packageName, + PackageManager.GET_PERMISSIONS + | PackageManager.MATCH_UNINSTALLED_PACKAGES); + } + } catch (NameNotFoundException ignored) { + } + } + if (mNewPackageInfo == null) { + Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart() + + " not available. Discontinuing installation"); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .setErrorDialogType(DLG_PACKAGE_ERROR) + .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, + PackageManager.INSTALL_FAILED_INVALID_APK)) + .setActivityResultCode(Activity.RESULT_FIRST_USER) + .build(); + } + mAppSnippet = getAppSnippet(mContext, mNewPackageInfo); + if (mLocalLOGV) { + Log.i(TAG, "Created snippet for " + mAppSnippet.getLabel()); + } + } + case ContentResolver.SCHEME_FILE -> { + File sourceFile = new File(packageUri.getPath()); + mNewPackageInfo = getPackageInfo(mContext, sourceFile, + PackageManager.GET_PERMISSIONS); + + // Check for parse errors + if (mNewPackageInfo == null) { + Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation"); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .setErrorDialogType(DLG_PACKAGE_ERROR) + .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, + PackageManager.INSTALL_FAILED_INVALID_APK)) + .setActivityResultCode(Activity.RESULT_FIRST_USER) + .build(); + } + if (mLocalLOGV) { + Log.i(TAG, "Creating snippet for local file " + sourceFile); + } + mAppSnippet = getAppSnippet(mContext, mNewPackageInfo.applicationInfo, sourceFile); + } + default -> { + Log.e(TAG, "Unexpected URI scheme " + packageUri); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + } + + return new InstallUserActionRequired.Builder( + USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet) + .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason)) + .setAppUpdating(isAppUpdating(mNewPackageInfo)) + .build(); + } + + /** + * Use the SessionInfo and set up the installer for pre-commit install session. + * + * @param sessionInfo The SessionInfo to compose + * @return {@code true} iff the installer could be set up + */ + private InstallStage processSessionInfo(@NonNull SessionInfo sessionInfo, + int userActionReason) { + mNewPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName()); + + mAppSnippet = getAppSnippet(mContext, sessionInfo); + return new InstallUserActionRequired.Builder( + USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet) + .setAppUpdating(isAppUpdating(mNewPackageInfo)) + .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason)) + .build(); + } + + private String getUpdateMessage(PackageInfo pkgInfo, int userActionReason) { + if (isAppUpdating(pkgInfo)) { + final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo); + final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage); + + if (!TextUtils.isEmpty(existingUpdateOwnerLabel) + && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) { + return mContext.getString(R.string.install_confirm_question_update_owner_reminder, + requestedUpdateOwnerLabel, existingUpdateOwnerLabel); + } + } + return null; + } + + private CharSequence getExistingUpdateOwnerLabel(PackageInfo pkgInfo) { + try { + final String packageName = pkgInfo.packageName; + final InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName); + final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName(); + return getApplicationLabel(existingUpdateOwner); + } catch (NameNotFoundException e) { + return null; + } + } + + private CharSequence getApplicationLabel(String packageName) { + try { + final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, + ApplicationInfoFlags.of(0)); + return mPackageManager.getApplicationLabel(appInfo); + } catch (NameNotFoundException e) { + return null; + } + } + + private boolean isAppUpdating(PackageInfo newPkgInfo) { + String pkgName = newPkgInfo.packageName; + // Check if there is already a package on the device with this name + // but it has been renamed to something else. + String[] oldName = mPackageManager.canonicalToCurrentPackageNames(new String[]{pkgName}); + if (oldName != null && oldName.length > 0 && oldName[0] != null) { + pkgName = oldName[0]; + newPkgInfo.packageName = pkgName; + newPkgInfo.applicationInfo.packageName = pkgName; + } + // Check if package is already installed. display confirmation dialog if replacing pkg + try { + // This is a little convoluted because we want to get all uninstalled + // apps, but this may include apps with just data, and if it is just + // data we still want to count it as "installed". + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkgName, + PackageManager.MATCH_UNINSTALLED_PACKAGES); + if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + return false; + } + } catch (NameNotFoundException e) { + return false; + } + return true; + } + + /** + * Once the user returns from Settings related to installing from unknown sources, reattempt + * the installation if the source app is granted permission to install other apps. Abort the + * installation if the source app is still not granted installing permission. + * @return {@link InstallUserActionRequired} containing data required to ask user confirmation + * to proceed with the install. + * {@link InstallAborted} if there was an error while recomputing, or the source still + * doesn't have install permission. + */ + public InstallStage reattemptInstall() { + InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo); + if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) { + // Source app now has appOp granted. + return generateConfirmationSnippet(); + } else if (unknownSourceStage.getStageCode() == InstallStage.STAGE_ABORTED) { + // There was some error in determining the AppOp code for the source app. + // Abort installation + return unknownSourceStage; + } else { + // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was + // unexpected while reattempting the install. Let's abort it. + Log.e(TAG, "AppOp still not granted."); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + } + + private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) { + if (requestInfo.getCallingPackage() == null) { + Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName); + return new InstallUserActionRequired.Builder( + USER_ACTION_REASON_ANONYMOUS_SOURCE, null) + .build(); + } + // Shouldn't use static constant directly, see b/65534401. + final String appOpStr = + AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES); + final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, + requestInfo.getOriginatingUid(), + requestInfo.getCallingPackage(), requestInfo.getAttributionTag(), + "Started package installation activity"); + + if (mLocalLOGV) { + Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode); + } + switch (appOpMode) { + case AppOpsManager.MODE_DEFAULT: + mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(), + requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED); + // fall through + case AppOpsManager.MODE_ERRORED: + try { + ApplicationInfo sourceInfo = + mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0); + AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo); + return new InstallUserActionRequired.Builder( + USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet) + .setDialogMessage(requestInfo.getCallingPackage()) + .build(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage()); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + case AppOpsManager.MODE_ALLOWED: + return new InstallReady(); + default: + Log.e(TAG, "Invalid app op mode " + appOpMode + + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + + requestInfo.getOriginatingUid()); + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + } + + + /** + * Kick off the installation. Register a broadcast listener to get the result of the + * installation and commit the staged session here. If the installation was session based, + * signal the PackageInstaller that the user has granted permission to proceed with the install + */ + public void initiateInstall() { + if (mSessionId > 0) { + mPackageInstaller.setPermissionsResult(mSessionId, true); + mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE) + .setActivityResultCode(Activity.RESULT_OK).build()); + return; + } + + Uri uri = mIntent.getData(); + if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) { + try { + mPackageManager.installExistingPackage(mNewPackageInfo.packageName); + setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1); + } catch (PackageManager.NameNotFoundException e) { + setStageBasedOnResult(PackageInstaller.STATUS_FAILURE, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1); + } + return; + } + + if (mStagedSessionId <= 0) { + // How did we even land here? + Log.e(TAG, "Invalid local session and caller initiated session"); + mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .build()); + return; + } + + int installId; + try { + mInstallResult.setValue(new InstallInstalling(mAppSnippet)); + installId = InstallEventReceiver.addObserver(mContext, + EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult); + } catch (OutOfIdsException e) { + setStageBasedOnResult(PackageInstaller.STATUS_FAILURE, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1); + return; + } + + Intent broadcastIntent = new Intent(BROADCAST_ACTION); + broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + broadcastIntent.setPackage(mContext.getPackageName()); + broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId); + + PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, installId, broadcastIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + + try { + PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId); + session.commit(pendingIntent.getIntentSender()); + } catch (Exception e) { + Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e); + mPackageInstaller.abandonSession(mStagedSessionId); + setStageBasedOnResult(PackageInstaller.STATUS_FAILURE, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1); + } + } + + private void setStageBasedOnResult(int statusCode, int legacyStatus, String message, + int serviceId) { + if (statusCode == PackageInstaller.STATUS_SUCCESS) { + boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); + + InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet) + .setShouldReturnResult(shouldReturnResult); + Intent resultIntent; + if (shouldReturnResult) { + resultIntent = new Intent() + .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED); + } else { + resultIntent = mPackageManager + .getLaunchIntentForPackage(mNewPackageInfo.packageName); + } + successBuilder.setResultIntent(resultIntent); + + mInstallResult.setValue(successBuilder.build()); + } else { + mInstallResult.setValue( + new InstallFailed(mAppSnippet, statusCode, legacyStatus, message)); + } + } + + public MutableLiveData<InstallStage> getInstallResult() { + return mInstallResult; + } + + /** + * Cleanup the staged session. Also signal the packageinstaller that an install session is to + * be aborted + */ + public void cleanupInstall() { + if (mSessionId > 0) { + mPackageInstaller.setPermissionsResult(mSessionId, false); + } else if (mStagedSessionId > 0) { + cleanupStagingSession(); + } + } + + /** + * When the identity of the install source could not be determined, user can skip checking the + * source and directly proceed with the install. + */ + public InstallStage forcedSkipSourceCheck() { + return generateConfirmationSnippet(); + } + public MutableLiveData<Integer> getStagingProgress() { if (mSessionStager != null) { return mSessionStager.getProgress(); @@ -373,4 +871,29 @@ public class InstallRepository { return mUid; } } + + public static class AppOpRequestInfo { + + private String mCallingPackage; + private String mAttributionTag; + private int mOrginatingUid; + + public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) { + mCallingPackage = callingPackage; + mOrginatingUid = orginatingUid; + mAttributionTag = attributionTag; + } + + public String getCallingPackage() { + return mCallingPackage; + } + + public String getAttributionTag() { + return mAttributionTag; + } + + public int getOriginatingUid() { + return mOrginatingUid; + } + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java index 82a8c9598051..9c15fd50f216 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java @@ -24,17 +24,23 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Process; import android.util.Log; import androidx.annotation.NonNull; +import java.io.File; import java.util.Arrays; +import java.util.Objects; public class PackageUtil { private static final String TAG = InstallRepository.class.getSimpleName(); private static final String DOWNLOADS_AUTHORITY = "downloads"; + private static final String SPLIT_BASE_APK_END_WITH = "base.apk"; /** * Determines if the UID belongs to the system downloads provider and returns the @@ -212,4 +218,228 @@ public class PackageUtil { int installerUid = sessionInfo.getInstallerUid(); return originatingUid == installerUid; } + + /** + * Generates a stub {@link PackageInfo} object for the given packageName + */ + public static PackageInfo generateStubPackageInfo(String packageName) { + final PackageInfo info = new PackageInfo(); + final ApplicationInfo aInfo = new ApplicationInfo(); + info.applicationInfo = aInfo; + info.packageName = info.applicationInfo.packageName = packageName; + return info; + } + + /** + * Generates an {@link AppSnippet} containing an appIcon and appLabel from the + * {@link SessionInfo} object + */ + public static AppSnippet getAppSnippet(Context context, SessionInfo info) { + PackageManager pm = context.getPackageManager(); + CharSequence label = info.getAppLabel(); + Drawable icon = info.getAppIcon() != null ? + new BitmapDrawable(context.getResources(), info.getAppIcon()) + : pm.getDefaultActivityIcon(); + return new AppSnippet(label, icon); + } + + /** + * Generates an {@link AppSnippet} containing an appIcon and appLabel from the + * {@link PackageInfo} object + */ + public static AppSnippet getAppSnippet(Context context, PackageInfo pkgInfo) { + return getAppSnippet(context, pkgInfo.applicationInfo); + } + + /** + * Generates an {@link AppSnippet} containing an appIcon and appLabel from the + * {@link ApplicationInfo} object + */ + public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo) { + PackageManager pm = context.getPackageManager(); + CharSequence label = pm.getApplicationLabel(appInfo); + Drawable icon = pm.getApplicationIcon(appInfo); + return new AppSnippet(label, icon); + } + + /** + * Generates an {@link AppSnippet} containing an appIcon and appLabel from the + * supplied APK file + */ + public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo, + File sourceFile) { + ApplicationInfo appInfoFromFile = processAppInfoForFile(appInfo, sourceFile); + CharSequence label = getAppLabelFromFile(context, appInfoFromFile); + Drawable icon = getAppIconFromFile(context, appInfoFromFile); + return new AppSnippet(label, icon); + } + + /** + * Utility method to load application label + * + * @param context context of package that can load the resources + * @param appInfo ApplicationInfo object of package whose resources are to be loaded + */ + public static CharSequence getAppLabelFromFile(Context context, ApplicationInfo appInfo) { + PackageManager pm = context.getPackageManager(); + CharSequence label = null; + // Try to load the label from the package's resources. If an app has not explicitly + // specified any label, just use the package name. + if (appInfo.labelRes != 0) { + try { + label = appInfo.loadLabel(pm); + } catch (Resources.NotFoundException e) { + } + } + if (label == null) { + label = (appInfo.nonLocalizedLabel != null) ? + appInfo.nonLocalizedLabel : appInfo.packageName; + } + return label; + } + + /** + * Utility method to load application icon + * + * @param context context of package that can load the resources + * @param appInfo ApplicationInfo object of package whose resources are to be loaded + */ + public static Drawable getAppIconFromFile(Context context, ApplicationInfo appInfo) { + PackageManager pm = context.getPackageManager(); + Drawable icon = null; + // Try to load the icon from the package's resources. If an app has not explicitly + // specified any resource, just use the default icon for now. + try { + if (appInfo.icon != 0) { + try { + icon = appInfo.loadIcon(pm); + } catch (Resources.NotFoundException e) { + } + } + if (icon == null) { + icon = context.getPackageManager().getDefaultActivityIcon(); + } + } catch (OutOfMemoryError e) { + Log.i(TAG, "Could not load app icon", e); + } + return icon; + } + + private static ApplicationInfo processAppInfoForFile(ApplicationInfo appInfo, File sourceFile) { + final String archiveFilePath = sourceFile.getAbsolutePath(); + appInfo.publicSourceDir = archiveFilePath; + + if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) { + final File[] files = sourceFile.getParentFile().listFiles(); + final String[] splits = Arrays.stream(appInfo.splitNames) + .map(i -> findFilePath(files, i + ".apk")) + .filter(Objects::nonNull) + .toArray(String[]::new); + + appInfo.splitSourceDirs = splits; + appInfo.splitPublicSourceDirs = splits; + } + return appInfo; + } + + private static String findFilePath(File[] files, String postfix) { + for (File file : files) { + final String path = file.getAbsolutePath(); + if (path.endsWith(postfix)) { + return path; + } + } + return null; + } + + /** + * @return the packageName corresponding to a UID. + */ + public static String getPackageNameForUid(Context context, int sourceUid, + String callingPackage) { + if (sourceUid == Process.INVALID_UID) { + return null; + } + // If the sourceUid belongs to the system downloads provider, we explicitly return the + // name of the Download Manager package. This is because its UID is shared with multiple + // packages, resulting in uncertainty about which package will end up first in the list + // of packages associated with this UID + PackageManager pm = context.getPackageManager(); + ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo( + pm, sourceUid); + if (systemDownloadProviderInfo != null) { + return systemDownloadProviderInfo.packageName; + } + String[] packagesForUid = pm.getPackagesForUid(sourceUid); + if (packagesForUid == null) { + return null; + } + if (packagesForUid.length > 1) { + if (callingPackage != null) { + for (String packageName : packagesForUid) { + if (packageName.equals(callingPackage)) { + return packageName; + } + } + } + Log.i(TAG, "Multiple packages found for source uid " + sourceUid); + } + return packagesForUid[0]; + } + + /** + * Utility method to get package information for a given {@link File} + */ + public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) { + String filePath = sourceFile.getAbsolutePath(); + if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) { + File dir = sourceFile.getParentFile(); + if (dir.listFiles().length > 1) { + // split apks, use file directory to get archive info + filePath = dir.getPath(); + } + } + try { + return context.getPackageManager().getPackageArchiveInfo(filePath, flags); + } catch (Exception ignored) { + return null; + } + } + + /** + * The class to hold an incoming package's icon and label. + * See {@link #getAppSnippet(Context, SessionInfo)}, + * {@link #getAppSnippet(Context, PackageInfo)}, + * {@link #getAppSnippet(Context, ApplicationInfo)}, + * {@link #getAppSnippet(Context, ApplicationInfo, File)} + */ + public static class AppSnippet { + + private CharSequence mLabel; + private Drawable mIcon; + + public AppSnippet(CharSequence label, Drawable icon) { + mLabel = label; + mIcon = icon; + } + + public AppSnippet() { + } + + public CharSequence getLabel() { + return mLabel; + } + + public void setLabel(CharSequence mLabel) { + this.mLabel = mLabel; + } + + public Drawable getIcon() { + return mIcon; + } + + public void setIcon(Drawable mIcon) { + this.mIcon = mIcon; + } + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/TemporaryFileManager.java new file mode 100644 index 000000000000..3a1c3973d474 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/TemporaryFileManager.java @@ -0,0 +1,92 @@ +/* + * 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 com.android.packageinstaller.v2.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.util.Log; +import androidx.annotation.NonNull; +import java.io.File; +import java.io.IOException; + +/** + * Manages files of the package installer and resets state during boot. + */ +public class TemporaryFileManager extends BroadcastReceiver { + + private static final String LOG_TAG = TemporaryFileManager.class.getSimpleName(); + + /** + * Create a new file to hold a staged file. + * + * @param context The context of the caller + * @return A new file + */ + @NonNull + public static File getStagedFile(@NonNull Context context) throws IOException { + return File.createTempFile("package", ".apk", context.getNoBackupFilesDir()); + } + + /** + * Get the file used to store the results of installs. + * + * @param context The context of the caller + * @return the file used to store the results of installs + */ + @NonNull + public static File getInstallStateFile(@NonNull Context context) { + return new File(context.getNoBackupFilesDir(), "install_results.xml"); + } + + /** + * Get the file used to store the results of uninstalls. + * + * @param context The context of the caller + * @return the file used to store the results of uninstalls + */ + @NonNull + public static File getUninstallStateFile(@NonNull Context context) { + return new File(context.getNoBackupFilesDir(), "uninstall_results.xml"); + } + + @Override + public void onReceive(Context context, Intent intent) { + long systemBootTime = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + + File[] filesOnBoot = context.getNoBackupFilesDir().listFiles(); + + if (filesOnBoot == null) { + return; + } + + for (int i = 0; i < filesOnBoot.length; i++) { + File fileOnBoot = filesOnBoot[i]; + + if (systemBootTime > fileOnBoot.lastModified()) { + boolean wasDeleted = fileOnBoot.delete(); + if (!wasDeleted) { + Log.w(LOG_TAG, "Could not delete " + fileOnBoot.getName() + " onBoot"); + } + } else { + Log.w(LOG_TAG, fileOnBoot.getName() + " was created before onBoot broadcast was " + + "received"); + } + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java index cc9857d45bc4..520b6c573acf 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java @@ -26,6 +26,8 @@ public class InstallAborted extends InstallStage { public static final int ABORT_REASON_INTERNAL_ERROR = 0; public static final int ABORT_REASON_POLICY = 1; + public static final int ABORT_REASON_DONE = 2; + public static final int DLG_PACKAGE_ERROR = 1; private final int mStage = InstallStage.STAGE_ABORTED; private final int mAbortReason; @@ -46,13 +48,15 @@ public class InstallAborted extends InstallStage { */ @Nullable private final Intent mIntent; + private final int mErrorDialogType; private final int mActivityResultCode; private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent, - int activityResultCode) { + int activityResultCode, int errorDialogType) { mAbortReason = reason; mMessage = message; mIntent = intent; + mErrorDialogType = errorDialogType; mActivityResultCode = activityResultCode; } @@ -70,6 +74,10 @@ public class InstallAborted extends InstallStage { return mIntent; } + public int getErrorDialogType() { + return mErrorDialogType; + } + public int getActivityResultCode() { return mActivityResultCode; } @@ -85,6 +93,7 @@ public class InstallAborted extends InstallStage { private String mMessage = ""; private Intent mIntent = null; private int mActivityResultCode = Activity.RESULT_CANCELED; + private int mErrorDialogType; public Builder(int reason) { mAbortReason = reason; @@ -100,13 +109,19 @@ public class InstallAborted extends InstallStage { return this; } + public Builder setErrorDialogType(int dialogType) { + mErrorDialogType = dialogType; + return this; + } + public Builder setActivityResultCode(int resultCode) { mActivityResultCode = resultCode; return this; } public InstallAborted build() { - return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode); + return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode, + mErrorDialogType); } } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java new file mode 100644 index 000000000000..67e169036551 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java @@ -0,0 +1,69 @@ +/* + * 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 com.android.packageinstaller.v2.model.installstagedata; + +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet; + +public class InstallFailed extends InstallStage { + + private final int mStage = InstallStage.STAGE_FAILED; + @NonNull + private final AppSnippet mAppSnippet; + private final int mStatusCode; + private final int mLegacyCode; + @Nullable + private final String mMessage; + + public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode, + @Nullable String message) { + mAppSnippet = appSnippet; + mLegacyCode = statusCode; + mStatusCode = legacyCode; + mMessage = message; + } + + @Override + public int getStageCode() { + return mStage; + } + + @NonNull + public Drawable getAppIcon() { + return mAppSnippet.getIcon(); + } + + @NonNull + public String getAppLabel() { + return (String) mAppSnippet.getLabel(); + } + + public int getStatusCode() { + return mStatusCode; + } + + public int getLegacyCode() { + return mLegacyCode; + } + + @Nullable + public String getMessage() { + return mMessage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java new file mode 100644 index 000000000000..efd4947f712f --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java @@ -0,0 +1,47 @@ +/* + * 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 com.android.packageinstaller.v2.model.installstagedata; + +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet; + +public class InstallInstalling extends InstallStage { + + private final int mStage = InstallStage.STAGE_INSTALLING; + @NonNull + private final AppSnippet mAppSnippet; + + public InstallInstalling(@NonNull AppSnippet appSnippet) { + mAppSnippet = appSnippet; + } + + @Override + public int getStageCode() { + return mStage; + } + + @NonNull + public Drawable getAppIcon() { + return mAppSnippet.getIcon(); + } + + @NonNull + public String getAppLabel() { + return (String) mAppSnippet.getLabel(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java new file mode 100644 index 000000000000..da482564c505 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java @@ -0,0 +1,95 @@ +/* + * 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 com.android.packageinstaller.v2.model.installstagedata; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet; + +public class InstallSuccess extends InstallStage { + + private final int mStage = InstallStage.STAGE_SUCCESS; + + @NonNull + private final AppSnippet mAppSnippet; + private final boolean mShouldReturnResult; + /** + * <p>If the caller is requesting a result back, this will hold the Intent with + * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p> + * <p>If the caller doesn't want the result back, this will hold the Intent that launches + * the newly installed / updated app.</p> + */ + @NonNull + private final Intent mResultIntent; + + public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult, + @NonNull Intent launcherIntent) { + mAppSnippet = appSnippet; + mShouldReturnResult = shouldReturnResult; + mResultIntent = launcherIntent; + } + + @Override + public int getStageCode() { + return mStage; + } + + @NonNull + public Drawable getAppIcon() { + return mAppSnippet.getIcon(); + } + + @NonNull + public String getAppLabel() { + return (String) mAppSnippet.getLabel(); + } + + public boolean shouldReturnResult() { + return mShouldReturnResult; + } + + @NonNull + public Intent getResultIntent() { + return mResultIntent; + } + + public static class Builder { + + private final AppSnippet mAppSnippet; + private boolean mShouldReturnResult; + private Intent mLauncherIntent; + + public Builder(@NonNull AppSnippet appSnippet) { + mAppSnippet = appSnippet; + } + + public Builder setShouldReturnResult(boolean returnResult) { + mShouldReturnResult = returnResult; + return this; + } + + public Builder setResultIntent(@NonNull Intent intent) { + mLauncherIntent = intent; + return this; + } + + public InstallSuccess build() { + return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java new file mode 100644 index 000000000000..08a7487c69d3 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java @@ -0,0 +1,99 @@ +/* + * 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 com.android.packageinstaller.v2.model.installstagedata; + +import android.graphics.drawable.Drawable; +import androidx.annotation.Nullable; +import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet; + +public class InstallUserActionRequired extends InstallStage { + + public static final int USER_ACTION_REASON_UNKNOWN_SOURCE = 0; + public static final int USER_ACTION_REASON_ANONYMOUS_SOURCE = 1; + public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2; + private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED; + private final int mActionReason; + @Nullable + private final AppSnippet mAppSnippet; + private final boolean mIsAppUpdating; + @Nullable + private final String mDialogMessage; + + public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet, + boolean isUpdating, @Nullable String dialogMessage) { + mActionReason = actionReason; + mAppSnippet = appSnippet; + mIsAppUpdating = isUpdating; + mDialogMessage = dialogMessage; + } + + @Override + public int getStageCode() { + return mStage; + } + + @Nullable + public Drawable getAppIcon() { + return mAppSnippet != null ? mAppSnippet.getIcon() : null; + } + + @Nullable + public String getAppLabel() { + return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null; + } + + public boolean isAppUpdating() { + return mIsAppUpdating; + } + + @Nullable + public String getDialogMessage() { + return mDialogMessage; + } + + public int getActionReason() { + return mActionReason; + } + + public static class Builder { + + private final int mActionReason; + private final AppSnippet mAppSnippet; + private boolean mIsAppUpdating; + private String mDialogMessage; + + public Builder(int actionReason, @Nullable AppSnippet appSnippet) { + mActionReason = actionReason; + mAppSnippet = appSnippet; + } + + public Builder setAppUpdating(boolean isUpdating) { + mIsAppUpdating = isUpdating; + return this; + } + + public Builder setDialogMessage(@Nullable String message) { + mDialogMessage = message; + return this; + } + + public InstallUserActionRequired build() { + return new InstallUserActionRequired(mActionReason, mAppSnippet, mIsAppUpdating, + mDialogMessage); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java new file mode 100644 index 000000000000..fdb024ffc23e --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java @@ -0,0 +1,38 @@ +/* + * 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 com.android.packageinstaller.v2.ui; + +import android.content.Intent; + +public interface InstallActionListener { + + /** + * Method to handle a positive response from the user + */ + void onPositiveResponse(int stageCode); + + /** + * Method to dispatch intent for toggling "install from unknown sources" setting for a package + */ + void sendUnknownAppsIntent(String packageName); + + /** + * Method to handle a negative response from the user + */ + void onNegativeResponse(int stageCode); + void openInstalledApp(Intent intent); +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java index ba5a0cdc7b0d..949355535b64 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java @@ -16,13 +16,22 @@ package com.android.packageinstaller.v2.ui; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; import static android.os.Process.INVALID_UID; import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR; import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY; +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.ActivityNotFoundException; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.UserManager; +import android.provider.Settings; import android.util.Log; import android.view.Window; import androidx.annotation.Nullable; @@ -34,13 +43,25 @@ import com.android.packageinstaller.R; import com.android.packageinstaller.v2.model.InstallRepository; import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo; import com.android.packageinstaller.v2.model.installstagedata.InstallAborted; +import com.android.packageinstaller.v2.model.installstagedata.InstallFailed; +import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling; import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess; +import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired; +import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment; +import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment; +import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment; +import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment; +import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment; import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment; +import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment; import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment; import com.android.packageinstaller.v2.viewmodel.InstallViewModel; import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory; +import java.util.ArrayList; +import java.util.List; -public class InstallLaunch extends FragmentActivity { +public class InstallLaunch extends FragmentActivity implements InstallActionListener { public static final String EXTRA_CALLING_PKG_UID = InstallLaunch.class.getPackageName() + ".callingPkgUid"; @@ -48,11 +69,17 @@ public class InstallLaunch extends FragmentActivity { InstallLaunch.class.getPackageName() + ".callingPkgName"; private static final String TAG = InstallLaunch.class.getSimpleName(); private static final String TAG_DIALOG = "dialog"; + private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1; private final boolean mLocalLOGV = false; + /** + * A collection of unknown sources listeners that are actively listening for app ops mode + * changes + */ + private final List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1); private InstallViewModel mInstallViewModel; private InstallRepository mInstallRepository; - private FragmentManager mFragmentManager; + private AppOpsManager mAppOpsManager; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -61,6 +88,8 @@ public class InstallLaunch extends FragmentActivity { this.requestWindowFeature(Window.FEATURE_NO_TITLE); mFragmentManager = getSupportFragmentManager(); + mAppOpsManager = getSystemService(AppOpsManager.class); + mInstallRepository = new InstallRepository(getApplicationContext()); mInstallViewModel = new ViewModelProvider(this, new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get( @@ -87,10 +116,44 @@ public class InstallLaunch extends FragmentActivity { InstallAborted aborted = (InstallAborted) installStage; switch (aborted.getAbortReason()) { // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR - case ABORT_REASON_INTERNAL_ERROR -> setResult(RESULT_CANCELED, true); - case ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted); - default -> setResult(RESULT_CANCELED, true); + case InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR -> + setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true); + case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted); + default -> setResult(RESULT_CANCELED, null, true); + } + } else if (installStage.getStageCode() == InstallStage.STAGE_USER_ACTION_REQUIRED) { + InstallUserActionRequired uar = (InstallUserActionRequired) installStage; + switch (uar.getActionReason()) { + case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION: + InstallConfirmationFragment actionDialog = new InstallConfirmationFragment(uar); + showDialogInner(actionDialog); + break; + case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE: + ExternalSourcesBlockedFragment externalSourceDialog = + new ExternalSourcesBlockedFragment(uar); + showDialogInner(externalSourceDialog); + break; + case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE: + AnonymousSourceFragment anonymousSourceDialog = new AnonymousSourceFragment(); + showDialogInner(anonymousSourceDialog); + } + } else if (installStage.getStageCode() == InstallStage.STAGE_INSTALLING) { + InstallInstalling installing = (InstallInstalling) installStage; + InstallInstallingFragment installingDialog = new InstallInstallingFragment(installing); + showDialogInner(installingDialog); + } else if (installStage.getStageCode() == InstallStage.STAGE_SUCCESS) { + InstallSuccess success = (InstallSuccess) installStage; + if (success.shouldReturnResult()) { + Intent successIntent = success.getResultIntent(); + setResult(Activity.RESULT_OK, successIntent, true); + } else { + InstallSuccessFragment successFragment = new InstallSuccessFragment(success); + showDialogInner(successFragment); } + } else if (installStage.getStageCode() == InstallStage.STAGE_FAILED) { + InstallFailed failed = (InstallFailed) installStage; + InstallFailedFragment failedDialog = new InstallFailedFragment(failed); + showDialogInner(failedDialog); } else { Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode()); showDialogInner(null); @@ -122,7 +185,7 @@ public class InstallLaunch extends FragmentActivity { shouldFinish = false; showDialogInner(blockedByPolicyDialog); } - setResult(RESULT_CANCELED, shouldFinish); + setResult(RESULT_CANCELED, null, shouldFinish); } /** @@ -161,12 +224,113 @@ public class InstallLaunch extends FragmentActivity { } } - public void setResult(int resultCode, boolean shouldFinish) { - // TODO: This is incomplete. We need to send RESULT_FIRST_USER, RESULT_OK etc - // for relevant use cases. Investigate when to send what result. - super.setResult(resultCode); + public void setResult(int resultCode, Intent data, boolean shouldFinish) { + super.setResult(resultCode, data); if (shouldFinish) { finish(); } } + + @Override + public void onPositiveResponse(int reasonCode) { + switch (reasonCode) { + case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> + mInstallViewModel.forcedSkipSourceCheck(); + case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> + mInstallViewModel.initiateInstall(); + } + } + + @Override + public void onNegativeResponse(int stageCode) { + if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) { + mInstallViewModel.cleanupInstall(); + } + setResult(Activity.RESULT_CANCELED, null, true); + } + + @Override + public void sendUnknownAppsIntent(String sourcePackageName) { + Intent settingsIntent = new Intent(); + settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); + final Uri packageUri = Uri.parse("package:" + sourcePackageName); + settingsIntent.setData(packageUri); + settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY); + + try { + registerAppOpChangeListener(new UnknownSourcesListener(sourcePackageName), + sourcePackageName); + startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE); + } catch (ActivityNotFoundException exc) { + Log.e(TAG, "Settings activity not found for action: " + + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); + } + } + + @Override + public void openInstalledApp(Intent intent) { + setResult(RESULT_OK, intent, true); + if (intent != null && intent.hasCategory(CATEGORY_LAUNCHER)) { + startActivity(intent); + } + } + + private void registerAppOpChangeListener(UnknownSourcesListener listener, String packageName) { + mAppOpsManager.startWatchingMode( + AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, packageName, + listener); + mActiveUnknownSourcesListeners.add(listener); + } + + private void unregisterAppOpChangeListener(UnknownSourcesListener listener) { + mActiveUnknownSourcesListeners.remove(listener); + mAppOpsManager.stopWatchingMode(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_TRUST_EXTERNAL_SOURCE) { + mInstallViewModel.reattemptInstall(); + } else { + setResult(Activity.RESULT_CANCELED, null, true); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + while (!mActiveUnknownSourcesListeners.isEmpty()) { + unregisterAppOpChangeListener(mActiveUnknownSourcesListeners.get(0)); + } + } + + private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener { + + private final String mOriginatingPackage; + + public UnknownSourcesListener(String originatingPackage) { + mOriginatingPackage = originatingPackage; + } + + @Override + public void onOpChanged(String op, String packageName) { + if (!mOriginatingPackage.equals(packageName)) { + return; + } + unregisterAppOpChangeListener(this); + mActiveUnknownSourcesListeners.remove(this); + if (isDestroyed()) { + return; + } + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (!isDestroyed()) { + // Bring Pia to the foreground. FLAG_ACTIVITY_REORDER_TO_FRONT will reuse the + // paused instance, so we don't unnecessarily create a new instance of Pia. + startActivity(getIntent() + .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)); + } + }, 500); + } + } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java new file mode 100644 index 000000000000..6d6fcc94faf7 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java @@ -0,0 +1,63 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired; +import com.android.packageinstaller.v2.ui.InstallActionListener; + +/** + * Dialog to show when the source of apk can not be identified. + */ +public class AnonymousSourceFragment extends DialogFragment { + + public static String TAG = AnonymousSourceFragment.class.getSimpleName(); + private InstallActionListener mInstallActionListener; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.anonymous_source_warning) + .setPositiveButton(R.string.anonymous_source_continue, + ((dialog, which) -> mInstallActionListener.onPositiveResponse( + InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE))) + .setNegativeButton(R.string.cancel, + ((dialog, which) -> mInstallActionListener.onNegativeResponse( + InstallStage.STAGE_USER_ACTION_REQUIRED))).create(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(InstallStage.STAGE_USER_ACTION_REQUIRED); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java new file mode 100644 index 000000000000..4cdce52e96ba --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java @@ -0,0 +1,72 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired; +import com.android.packageinstaller.v2.ui.InstallActionListener; + +/** + * Dialog to show when the installing app is an unknown source and needs AppOp grant to install + * other apps. + */ +public class ExternalSourcesBlockedFragment extends DialogFragment { + + private final String TAG = ExternalSourcesBlockedFragment.class.getSimpleName(); + private final InstallUserActionRequired mDialogData; + private InstallActionListener mInstallActionListener; + + public ExternalSourcesBlockedFragment(InstallUserActionRequired dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + return new AlertDialog.Builder(requireContext()) + .setTitle(mDialogData.getAppLabel()) + .setIcon(mDialogData.getAppIcon()) + .setMessage(R.string.untrusted_external_source_warning) + .setPositiveButton(R.string.external_sources_settings, + (dialog, which) -> mInstallActionListener.sendUnknownAppsIntent( + mDialogData.getDialogMessage())) + .setNegativeButton(R.string.cancel, + (dialog, which) -> mInstallActionListener.onNegativeResponse( + mDialogData.getStageCode())) + .create(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(mDialogData.getStageCode()); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java new file mode 100644 index 000000000000..6398aef5d573 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java @@ -0,0 +1,95 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.Html; +import android.view.View; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired; +import com.android.packageinstaller.v2.ui.InstallActionListener; + +/** + * Dialog to show when the requesting user confirmation for installing an app. + */ +public class InstallConfirmationFragment extends DialogFragment { + + public static String TAG = InstallConfirmationFragment.class.getSimpleName(); + + @NonNull + private final InstallUserActionRequired mDialogData; + @NonNull + private InstallActionListener mInstallActionListener; + + public InstallConfirmationFragment(@NonNull InstallUserActionRequired dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null); + + AlertDialog dialog = new AlertDialog.Builder(requireContext()) + .setIcon(mDialogData.getAppIcon()) + .setTitle(mDialogData.getAppLabel()) + .setView(dialogView) + .setPositiveButton(mDialogData.isAppUpdating() ? R.string.update : R.string.install, + (dialogInt, which) -> mInstallActionListener.onPositiveResponse( + InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION)) + .setNegativeButton(R.string.cancel, + (dialogInt, which) -> mInstallActionListener.onNegativeResponse( + mDialogData.getStageCode())) + + .create(); + + // TODO: Dynamically change positive button text to update anyway + TextView viewToEnable; + if (mDialogData.isAppUpdating()) { + viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update); + String dialogMessage = mDialogData.getDialogMessage(); + if (dialogMessage != null) { + viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY)); + } + } else { + viewToEnable = dialogView.requireViewById(R.id.install_confirm_question); + } + viewToEnable.setVisibility(View.VISIBLE); + + return dialog; + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(mDialogData.getStageCode()); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java new file mode 100644 index 000000000000..d45cd76b2f2a --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java @@ -0,0 +1,107 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageInstaller; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallFailed; +import com.android.packageinstaller.v2.ui.InstallActionListener; + +/** + * Dialog to show when the installation failed. Depending on the failure code, an appropriate + * message would be shown to the user. This dialog is shown only when the caller does not want the + * install result back. + */ +public class InstallFailedFragment extends DialogFragment { + + private static final String TAG = InstallFailedFragment.class.getSimpleName(); + private final InstallFailed mDialogData; + private InstallActionListener mInstallActionListener; + + public InstallFailedFragment(InstallFailed dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null); + AlertDialog dialog = new AlertDialog.Builder(requireContext()) + .setTitle(mDialogData.getAppLabel()) + .setIcon(mDialogData.getAppIcon()) + .setView(dialogView) + .setPositiveButton(R.string.done, + (dialogInt, which) -> mInstallActionListener.onNegativeResponse( + mDialogData.getStageCode())) + .create(); + setExplanationFromErrorCode(mDialogData.getStatusCode(), dialogView); + + return dialog; + } + + /** + * Unhide the appropriate label for the statusCode. + * + * @param statusCode The status code from the package installer. + */ + private void setExplanationFromErrorCode(int statusCode, View dialogView) { + Log.d(TAG, "Installation status code: " + statusCode); + + View viewToEnable; + switch (statusCode) { + case PackageInstaller.STATUS_FAILURE_BLOCKED: + viewToEnable = dialogView.requireViewById(R.id.install_failed_blocked); + break; + case PackageInstaller.STATUS_FAILURE_CONFLICT: + viewToEnable = dialogView.requireViewById(R.id.install_failed_conflict); + break; + case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE: + viewToEnable = dialogView.requireViewById(R.id.install_failed_incompatible); + break; + case PackageInstaller.STATUS_FAILURE_INVALID: + viewToEnable = dialogView.requireViewById(R.id.install_failed_invalid_apk); + break; + default: + viewToEnable = dialogView.requireViewById(R.id.install_failed); + break; + } + + viewToEnable.setVisibility(View.VISIBLE); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(mDialogData.getStageCode()); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java new file mode 100644 index 000000000000..9f60f96bdfac --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling; + +/** + * Dialog to show when an install is in progress. + */ +public class InstallInstallingFragment extends DialogFragment { + + private final InstallInstalling mDialogData; + private AlertDialog mDialog; + + public InstallInstallingFragment(InstallInstalling dialogData) { + mDialogData = dialogData; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null); + mDialog = new AlertDialog.Builder(requireContext()) + .setTitle(mDialogData.getAppLabel()) + .setIcon(mDialogData.getAppIcon()) + .setView(dialogView) + .setNegativeButton(R.string.cancel, null) + .create(); + + dialogView.requireViewById(R.id.installing).setVisibility(View.VISIBLE); + this.setCancelable(false); + + return mDialog; + } + + @Override + public void onStart() { + super.onStart(); + mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setEnabled(false); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java new file mode 100644 index 000000000000..ab6a93222d48 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java @@ -0,0 +1,100 @@ +/* + * 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 com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess; +import com.android.packageinstaller.v2.ui.InstallActionListener; +import java.util.List; + +/** + * Dialog to show on a successful installation. This dialog is shown only when the caller does not + * want the install result back. + */ +public class InstallSuccessFragment extends DialogFragment { + + private final InstallSuccess mDialogData; + private AlertDialog mDialog; + private InstallActionListener mInstallActionListener; + private PackageManager mPm; + + public InstallSuccessFragment(InstallSuccess dialogData) { + mDialogData = dialogData; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + mPm = context.getPackageManager(); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null); + mDialog = new AlertDialog.Builder(requireContext()).setTitle(mDialogData.getAppLabel()) + .setIcon(mDialogData.getAppIcon()).setView(dialogView).setNegativeButton(R.string.done, + (dialog, which) -> mInstallActionListener.onNegativeResponse( + InstallStage.STAGE_SUCCESS)) + .setPositiveButton(R.string.launch, (dialog, which) -> { + }).create(); + + dialogView.requireViewById(R.id.install_success).setVisibility(View.VISIBLE); + + return mDialog; + } + + @Override + public void onStart() { + super.onStart(); + Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); + boolean enabled = false; + if (mDialogData.getResultIntent() != null) { + List<ResolveInfo> list = mPm.queryIntentActivities(mDialogData.getResultIntent(), 0); + if (list.size() > 0) { + enabled = true; + } + } + if (enabled) { + launchButton.setOnClickListener(view -> { + mInstallActionListener.openInstalledApp(mDialogData.getResultIntent()); + }); + } else { + launchButton.setEnabled(false); + } + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(mDialogData.getStageCode()); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java index dce0b9abd035..47fd67f0cf6b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java @@ -16,36 +16,47 @@ package com.android.packageinstaller.v2.ui.fragments; -import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.ui.InstallActionListener; public class SimpleErrorFragment extends DialogFragment { private static final String TAG = SimpleErrorFragment.class.getSimpleName(); private final int mMessageResId; + private InstallActionListener mInstallActionListener; public SimpleErrorFragment(int messageResId) { mMessageResId = messageResId; } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInstallActionListener = (InstallActionListener) context; + } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setMessage(mMessageResId) - .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish()) + .setPositiveButton(R.string.ok, + (dialog, which) -> + mInstallActionListener.onNegativeResponse(InstallStage.STAGE_ABORTED)) .create(); } @Override public void onCancel(DialogInterface dialog) { - getActivity().setResult(Activity.RESULT_CANCELED); - getActivity().finish(); + super.onCancel(dialog); + mInstallActionListener.onNegativeResponse(InstallStage.STAGE_ABORTED); } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java index 42b30234288d..759f4689996f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java @@ -58,7 +58,7 @@ public class InstallViewModel extends AndroidViewModel { if (installStage.getStageCode() != InstallStage.STAGE_READY) { mCurrentInstallStage.setValue(installStage); } else { - // Proceed with user confirmation here. + checkIfAllowedAndInitiateInstall(); } }); } @@ -67,4 +67,35 @@ public class InstallViewModel extends AndroidViewModel { public MutableLiveData<Integer> getStagingProgress() { return mRepository.getStagingProgress(); } + + private void checkIfAllowedAndInitiateInstall() { + InstallStage stage = mRepository.requestUserConfirmation(); + mCurrentInstallStage.setValue(stage); + } + + public void forcedSkipSourceCheck() { + InstallStage stage = mRepository.forcedSkipSourceCheck(); + mCurrentInstallStage.setValue(stage); + } + + public void cleanupInstall() { + mRepository.cleanupInstall(); + } + + public void reattemptInstall() { + InstallStage stage = mRepository.reattemptInstall(); + mCurrentInstallStage.setValue(stage); + } + + public void initiateInstall() { + // Since installing is an async operation, we will get the install result later in time. + // Result of the installation will be set in InstallRepository#mInstallResult. + // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source + mRepository.initiateInstall(); + mCurrentInstallStage.addSource(mRepository.getInstallResult(), installStage -> { + if (installStage != null) { + mCurrentInstallStage.setValue(installStage); + } + }); + } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index b1e1585d4250..90c7d46c3004 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -49,6 +49,7 @@ import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvid import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider +import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver @@ -100,6 +101,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, CardPageProvider, + CopyablePageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index f52ceec41253..1d897f77011e 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -44,6 +44,7 @@ import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider +import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.widget.scaffold.HomeScaffold @@ -71,6 +72,7 @@ object HomePageProvider : SettingsPageProvider { AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt new file mode 100644 index 000000000000..f897d8c58030 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt @@ -0,0 +1,61 @@ +/* + * 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 com.android.settingslib.spa.gallery.ui + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.EntrySearchData +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.CopyableBody + +private const val TITLE = "Sample Copyable" + +object CopyablePageProvider : SettingsPageProvider { + override val name = "Copyable" + + private val owner = createSettingsPage() + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + .setSearchDataFn { EntrySearchData(title = TITLE) } + } + + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + CopyableBody(body = "Copyable body") + } + } + } +} diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 905640f1ca4b..b40e911bd8de 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.1.3" +agp = "8.1.4" compose-compiler = "1.5.1" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt index 1cbdc33d5a4e..ae85675ab1b8 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt @@ -94,3 +94,18 @@ class SettingsScreenshotTestRule( screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) } } + +/** Create a [SettingsScreenshotTestRule] for settings screenshot tests. */ +fun settingsScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, +): SettingsScreenshotTestRule { + val assetPath = if (Build.FINGERPRINT.contains("robolectric")) { + "frameworks/base/packages/SettingsLib/Spa/screenshot/robotests/assets" + } else { + "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" + } + return SettingsScreenshotTestRule( + emulationSpec, + assetPath + ) +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt index 2cb6044d9bb9..8f762f606f18 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt @@ -20,7 +20,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Launch import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.WarningAmber -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.button.ActionButton import com.android.settingslib.spa.widget.button.ActionButtons import org.junit.Rule @@ -42,9 +42,8 @@ class ActionButtonsScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt index 7ef9f106a082..d766425fc12b 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt @@ -17,7 +17,7 @@ package com.android.settingslib.spa.screenshot.widget.chart import androidx.compose.material3.MaterialTheme -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.chart.BarChart import com.android.settingslib.spa.widget.chart.BarChartData import com.android.settingslib.spa.widget.chart.BarChartModel @@ -41,9 +41,8 @@ class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt index 3790164612c0..495bcbcc051e 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt @@ -16,7 +16,7 @@ package com.android.settingslib.spa.screenshot.widget.chart -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.chart.LineChart import com.android.settingslib.spa.widget.chart.LineChartData import com.android.settingslib.spa.widget.chart.LineChartModel @@ -41,9 +41,8 @@ class LineChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt index 3c3cc85b135d..ee61aadd6262 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt @@ -16,7 +16,7 @@ package com.android.settingslib.spa.screenshot.widget.chart -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.chart.PieChart import com.android.settingslib.spa.widget.chart.PieChartData import com.android.settingslib.spa.widget.chart.PieChartModel @@ -39,9 +39,8 @@ class PieChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt index 616b22525b09..94d032cbd507 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt @@ -17,7 +17,7 @@ package com.android.settingslib.spa.screenshot.widget.illustration import com.android.settingslib.spa.screenshot.R -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.illustration.Illustration import com.android.settingslib.spa.widget.illustration.IllustrationModel import com.android.settingslib.spa.widget.illustration.ResourceType @@ -40,9 +40,8 @@ class ImageIllustrationScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt index 8dd4ce7fec2f..2a01d8496158 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt @@ -17,7 +17,7 @@ package com.android.settingslib.spa.screenshot.widget.preference import androidx.compose.foundation.layout.Column -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.MainSwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import org.junit.Rule @@ -39,9 +39,8 @@ class MainSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt index 1e1a785bea21..4d8650efe201 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt @@ -21,7 +21,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Autorenew import androidx.compose.material.icons.outlined.DisabledByDefault import androidx.compose.runtime.Composable -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.ui.SettingsIcon @@ -48,9 +48,8 @@ class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt index d1878a744882..3983cc08ebc3 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt @@ -21,7 +21,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.SystemUpdate import androidx.compose.runtime.Composable -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.ProgressBarPreference import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference @@ -45,9 +45,8 @@ class ProgressBarPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt index c9f098bd77e3..3a96a7090d9b 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt @@ -19,7 +19,7 @@ package com.android.settingslib.spa.screenshot.widget.preference import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccessAlarm -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.SliderPreference import com.android.settingslib.spa.widget.preference.SliderPreferenceModel import org.junit.Rule @@ -41,9 +41,8 @@ class SliderPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt index eca40fbfee45..4a8064ac21d4 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt @@ -20,7 +20,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AirplanemodeActive import androidx.compose.runtime.Composable -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.ui.SettingsIcon @@ -43,9 +43,8 @@ class SwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt index f81a59fef79c..91b7b2429fca 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt @@ -18,7 +18,7 @@ package com.android.settingslib.spa.screenshot.widget.preference import androidx.compose.foundation.layout.Column import com.android.settingslib.spa.framework.compose.stateOf -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import org.junit.Rule @@ -40,9 +40,8 @@ class TwoTargetSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt index 98a4288d7e53..6ba010f5f96f 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt @@ -16,7 +16,7 @@ package com.android.settingslib.spa.screenshot.widget.ui -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.ui.Footer import org.junit.Rule import org.junit.Test @@ -37,9 +37,8 @@ class FooterScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt index 5417095260f3..320b20703c38 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt @@ -16,7 +16,7 @@ package com.android.settingslib.spa.screenshot.widget.ui -import com.android.settingslib.spa.screenshot.util.SettingsScreenshotTestRule +import com.android.settingslib.spa.screenshot.util.settingsScreenshotTestRule import com.android.settingslib.spa.widget.ui.Spinner import com.android.settingslib.spa.widget.ui.SpinnerOption import org.junit.Rule @@ -38,9 +38,8 @@ class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = - SettingsScreenshotTestRule( + settingsScreenshotTestRule( emulationSpec, - "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" ) @Test diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt new file mode 100644 index 000000000000..930d0a1872ab --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt @@ -0,0 +1,102 @@ +/* + * 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 com.android.settingslib.spa.widget.ui + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.DpOffset +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +@Composable +fun CopyableBody(body: String) { + var expanded by remember { mutableStateOf(false) } + var dpOffset by remember { mutableStateOf(DpOffset.Unspecified) } + + Box(modifier = Modifier + .fillMaxWidth() + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + dpOffset = DpOffset(it.x.toDp(), it.y.toDp()) + expanded = true + }, + ) + } + ) { + SettingsBody(body) + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + offset = dpOffset, + ) { + DropdownMenuTitle(body) + DropdownMenuCopy(body) { expanded = false } + } + } +} + +@Composable +private fun DropdownMenuTitle(text: String) { + Text( + text = text, + modifier = Modifier + .padding(MenuDefaults.DropdownMenuItemContentPadding) + .padding( + top = SettingsDimension.itemPaddingAround, + bottom = SettingsDimension.buttonPaddingVertical, + ), + color = SettingsTheme.colorScheme.categoryTitle, + style = MaterialTheme.typography.labelMedium, + ) +} + +@Composable +private fun DropdownMenuCopy(body: String, onCopy: () -> Unit) { + val clipboardManager = LocalClipboardManager.current + DropdownMenuItem( + text = { + Text( + text = stringResource(android.R.string.copy), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + }, + onClick = { + onCopy() + clipboardManager.setText(AnnotatedString(body)) + } + ) +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt new file mode 100644 index 000000000000..71072a5d58c5 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt @@ -0,0 +1,83 @@ +/* + * 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 com.android.settingslib.spa.widget.ui + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.longClick +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CopyableBodyTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun text_isDisplayed() { + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun onLongPress_contextMenuDisplayed() { + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).performTouchInput { + longClick() + } + + composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).assertIsDisplayed() + } + + @Test + fun onCopy_saveToClipboard() { + val clipboardManager = context.getSystemService(ClipboardManager::class.java)!! + clipboardManager.setPrimaryClip(ClipData.newPlainText("", "")) + composeTestRule.setContent { + CopyableBody(TEXT) + } + + composeTestRule.onNodeWithText(TEXT).performTouchInput { + longClick() + } + composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick() + + assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT) + } + + private companion object { + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index fc10a27cfa5b..45295b013cf9 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -36,10 +36,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import com.android.settingslib.development.DevelopmentSettingsEnabler import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.ui.CopyableBody import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle import com.android.settingslib.spaprivileged.R @@ -71,26 +71,38 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { @Composable private fun InstallType(app: ApplicationInfo) { if (!app.isInstantApp) return - Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(stringResource(com.android.settingslib.widget.preference.app.R.string.install_type_instant)) + Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall)) + SettingsBody( + stringResource( + com.android.settingslib.widget.preference.app.R.string.install_type_instant + ) + ) } @Composable private fun AppVersion() { - if (packageInfo.versionName == null) return - Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(packageInfo.versionNameBidiWrapped) + val versionName = packageInfo.versionNameBidiWrapped ?: return + Spacer(modifier = Modifier.height(SettingsDimension.paddingSmall)) + SettingsBody(versionName) } @Composable fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) { - if (packageInfo.versionName == null) return - HorizontalDivider() - Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { - SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped)) + val context = LocalContext.current + val footer = remember(showPackageName) { + val list = mutableListOf<String>() + packageInfo.versionNameBidiWrapped?.let { + list += context.getString(R.string.version_text, it) + } if (showPackageName) { - SettingsBody(packageInfo.packageName) + list += packageInfo.packageName } + list.joinToString(separator = System.lineSeparator()) + } + if (footer.isBlank()) return + HorizontalDivider() + Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + CopyableBody(footer) } } @@ -104,7 +116,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { private companion object { /** Wrapped the version name, so its directionality still keep same when RTL. */ - val PackageInfo.versionNameBidiWrapped: String + val PackageInfo.versionNameBidiWrapped: String? get() = BidiFormatter.getInstance().unicodeWrap(versionName) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 785f77981872..36c91f463efe 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -45,7 +45,7 @@ import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode -import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference +import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel import kotlinx.coroutines.flow.Flow private const val ENTRY_NAME = "AppList" @@ -157,7 +157,7 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( } val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions) val allowed = listModel.isAllowed(record) - return RestrictedSwitchPreference.getSummary( + return RestrictedSwitchPreferenceModel.getSummary( context = context, restrictedModeSupplier = { restrictedMode }, summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) }, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt new file mode 100644 index 000000000000..125f636507a8 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreference.kt @@ -0,0 +1,47 @@ +/* + * 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 com.android.settingslib.spaprivileged.template.preference + +import androidx.annotation.VisibleForTesting +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.widget.preference.MainSwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper + +@Composable +fun RestrictedMainSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) { + RestrictedMainSwitchPreference(model, restrictions, ::RestrictionsProviderImpl) +} + +@VisibleForTesting +@Composable +internal fun RestrictedMainSwitchPreference( + model: SwitchPreferenceModel, + restrictions: Restrictions, + restrictionsProviderFactory: RestrictionsProviderFactory, +) { + if (restrictions.keys.isEmpty()) { + MainSwitchPreference(model) + return + } + restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) { + MainSwitchPreference(it) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index e41976f79f8f..d5c5574a0450 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -16,29 +16,14 @@ package com.android.settingslib.spaprivileged.template.preference -import android.content.Context import androidx.annotation.VisibleForTesting -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.toggleableState -import androidx.compose.ui.state.ToggleableState import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder -import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted -import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin -import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted -import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl -import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode +import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper @Composable fun RestrictedSwitchPreference( @@ -59,91 +44,7 @@ internal fun RestrictedSwitchPreference( SwitchPreference(model) return } - val context = LocalContext.current - val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value - val restrictedSwitchModel = remember(restrictedMode) { - RestrictedSwitchPreferenceModel(context, model, restrictedMode) - } - restrictedSwitchModel.RestrictionWrapper { - SwitchPreference(restrictedSwitchModel) - } -} - -internal object RestrictedSwitchPreference { - fun getSummary( - context: Context, - restrictedModeSupplier: () -> RestrictedMode?, - summaryIfNoRestricted: () -> String, - checked: () -> Boolean?, - ): () -> String = { - when (val restrictedMode = restrictedModeSupplier()) { - is NoRestricted -> summaryIfNoRestricted() - is BaseUserRestricted -> context.getString(com.android.settingslib.R.string.disabled) - is BlockedByAdmin -> restrictedMode.getSummary(checked()) - null -> context.getPlaceholder() - } - } -} - -private class RestrictedSwitchPreferenceModel( - context: Context, - model: SwitchPreferenceModel, - private val restrictedMode: RestrictedMode?, -) : SwitchPreferenceModel { - override val title = model.title - - override val summary = RestrictedSwitchPreference.getSummary( - context = context, - restrictedModeSupplier = { restrictedMode }, - summaryIfNoRestricted = model.summary, - checked = model.checked, - ) - - override val checked = when (restrictedMode) { - null -> ({ null }) - is NoRestricted -> model.checked - is BaseUserRestricted -> ({ false }) - is BlockedByAdmin -> model.checked - } - - override val changeable = when (restrictedMode) { - null -> ({ false }) - is NoRestricted -> model.changeable - is BaseUserRestricted -> ({ false }) - is BlockedByAdmin -> ({ false }) - } - - override val onCheckedChange = when (restrictedMode) { - null -> null - is NoRestricted -> model.onCheckedChange - // Need to passthrough onCheckedChange for toggleable semantics, although since changeable - // is false so this will not be called. - is BaseUserRestricted -> model.onCheckedChange - // Pass null since semantics ToggleableState is provided in RestrictionWrapper. - is BlockedByAdmin -> null - } - - @Composable - fun RestrictionWrapper(content: @Composable () -> Unit) { - if (restrictedMode !is BlockedByAdmin) { - content() - return - } - Box( - Modifier - .clickable( - role = Role.Switch, - onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, - ) - .semantics { - this.toggleableState = ToggleableState(checked()) - }, - ) { content() } - } - - private fun ToggleableState(value: Boolean?) = when (value) { - true -> ToggleableState.On - false -> ToggleableState.Off - null -> ToggleableState.Indeterminate + restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) { + SwitchPreference(it) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt new file mode 100644 index 000000000000..fa44ecb92ed5 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt @@ -0,0 +1,130 @@ +/* + * 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 com.android.settingslib.spaprivileged.template.preference + +import android.content.Context +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.state.ToggleableState +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode + +internal class RestrictedSwitchPreferenceModel( + context: Context, + model: SwitchPreferenceModel, + private val restrictedMode: RestrictedMode?, +) : SwitchPreferenceModel { + override val title = model.title + + override val summary = getSummary( + context = context, + restrictedModeSupplier = { restrictedMode }, + summaryIfNoRestricted = model.summary, + checked = model.checked, + ) + + override val checked = when (restrictedMode) { + null -> ({ null }) + is NoRestricted -> model.checked + is BaseUserRestricted -> ({ false }) + is BlockedByAdmin -> model.checked + } + + override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false }) + + override val onCheckedChange = when (restrictedMode) { + null -> null + is NoRestricted -> model.onCheckedChange + // Need to passthrough onCheckedChange for toggleable semantics, although since changeable + // is false so this will not be called. + is BaseUserRestricted -> model.onCheckedChange + // Pass null since semantics ToggleableState is provided in RestrictionWrapper. + is BlockedByAdmin -> null + } + + @Composable + fun RestrictionWrapper(content: @Composable () -> Unit) { + if (restrictedMode !is BlockedByAdmin) { + content() + return + } + Box( + Modifier + .clickable( + role = Role.Switch, + onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, + ) + .semantics { + this.toggleableState = ToggleableState(checked()) + }, + ) { content() } + } + + private fun ToggleableState(value: Boolean?) = when (value) { + true -> ToggleableState.On + false -> ToggleableState.Off + null -> ToggleableState.Indeterminate + } + + companion object { + @Composable + fun RestrictionsProviderFactory.RestrictedSwitchWrapper( + model: SwitchPreferenceModel, + restrictions: Restrictions, + content: @Composable (SwitchPreferenceModel) -> Unit, + ) { + val context = LocalContext.current + val restrictedMode = rememberRestrictedMode(restrictions).value + val restrictedSwitchPreferenceModel = remember(restrictedMode) { + RestrictedSwitchPreferenceModel(context, model, restrictedMode) + } + restrictedSwitchPreferenceModel.RestrictionWrapper { + content(restrictedSwitchPreferenceModel) + } + } + + fun getSummary( + context: Context, + restrictedModeSupplier: () -> RestrictedMode?, + summaryIfNoRestricted: () -> String, + checked: () -> Boolean?, + ): () -> String = { + when (val restrictedMode = restrictedModeSupplier()) { + is NoRestricted -> summaryIfNoRestricted() + is BaseUserRestricted -> + context.getString(com.android.settingslib.R.string.disabled) + + is BlockedByAdmin -> restrictedMode.getSummary(checked()) + null -> context.getPlaceholder() + } + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt index ab34f6820014..72a5bd76e737 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt @@ -105,7 +105,8 @@ class AppInfoTest { } } - composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed() + composeTestRule.onNodeWithText(text = "version $VERSION_NAME", substring = true) + .assertIsDisplayed() } @Test @@ -119,10 +120,10 @@ class AppInfoTest { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - appInfoProvider.FooterAppVersion(true) + appInfoProvider.FooterAppVersion(showPackageName = true) } } - composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed() + composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertIsDisplayed() } @@ -137,10 +138,10 @@ class AppInfoTest { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { - appInfoProvider.FooterAppVersion(false) + appInfoProvider.FooterAppVersion(showPackageName = false) } } - composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist() + composeTestRule.onNodeWithText(text = PACKAGE_NAME, substring = true).assertDoesNotExist() } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt new file mode 100644 index 000000000000..55c16bd20336 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt @@ -0,0 +1,157 @@ +/* + * 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 com.android.settingslib.spaprivileged.template.preference + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.isOff +import androidx.compose.ui.test.isOn +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin +import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RestrictedMainSwitchPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val fakeBlockedByAdmin = FakeBlockedByAdmin() + + private val fakeRestrictionsProvider = FakeRestrictionsProvider() + + private val switchPreferenceModel = object : SwitchPreferenceModel { + override val title = TITLE + private val checkedState = mutableStateOf(true) + override val checked = { checkedState.value } + override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it } + } + + @Test + fun whenRestrictionsKeysIsEmpty_enabled() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + composeTestRule.onNode(isOn()).assertIsDisplayed() + } + + @Test + fun whenRestrictionsKeysIsEmpty_toggleable() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + composeTestRule.onNode(isOff()).assertIsDisplayed() + } + + @Test + fun whenNoRestricted_enabled() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = NoRestricted + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + composeTestRule.onNode(isOn()).assertIsDisplayed() + } + + @Test + fun whenNoRestricted_toggleable() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = NoRestricted + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + composeTestRule.onNode(isOff()).assertIsDisplayed() + } + + @Test + fun whenBaseUserRestricted_disabled() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled() + composeTestRule.onNode(isOff()).assertIsDisplayed() + } + + @Test + fun whenBaseUserRestricted_notToggleable() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + composeTestRule.onNode(isOff()).assertIsDisplayed() + } + + @Test + fun whenBlockedByAdmin_disabled() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + + setContent(restrictions) + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() + composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertDoesNotExist() + composeTestRule.onNode(isOn()).assertIsDisplayed() + } + + @Test + fun whenBlockedByAdmin_click() { + val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) + fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + + setContent(restrictions) + composeTestRule.onRoot().performClick() + + assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue() + } + + private fun setContent(restrictions: Restrictions) { + composeTestRule.setContent { + RestrictedMainSwitchPreference(switchPreferenceModel, restrictions) { _, _ -> + fakeRestrictionsProvider + } + } + } + + private companion object { + const val TITLE = "Title" + const val USER_ID = 0 + const val RESTRICTION_KEY = "restriction_key" + } +} diff --git a/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml new file mode 100644 index 000000000000..bcf5b91797d1 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_bt_untethered_earbuds.xml @@ -0,0 +1,29 @@ +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal" > + <path + android:fillColor="#4E4639" + android:pathData="M21.818,18.14V15.227C21.818,12.522 19.615,10.318 16.909,10.318C14.204,10.318 12,12.522 12,15.227V19.591C12,22.296 14.204,24.5 16.909,24.5C17.662,24.5 18.382,24.326 19.025,24.02C19.538,24.326 20.127,24.5 20.727,24.5C22.527,24.5 24,23.028 24,21.228C24,19.809 23.084,18.587 21.818,18.14ZM16.909,12.5C18.414,12.5 19.636,13.722 19.636,15.227C19.636,16.733 18.414,17.955 16.909,17.955C15.404,17.955 14.182,16.733 14.182,15.227C14.182,13.722 15.404,12.5 16.909,12.5ZM14.182,19.591V19.308C14.967,19.831 15.906,20.136 16.909,20.136C17.913,20.136 18.851,19.831 19.636,19.308V19.591C19.636,19.635 19.636,19.678 19.636,19.711C19.636,19.722 19.636,19.733 19.636,19.744C19.636,19.777 19.636,19.82 19.625,19.853C19.625,19.864 19.625,19.886 19.625,19.896C19.625,19.918 19.615,19.951 19.615,19.973C19.615,19.995 19.604,20.028 19.604,20.049C19.604,20.06 19.593,20.082 19.593,20.093C19.549,20.3 19.494,20.497 19.407,20.693C19.385,20.736 19.364,20.78 19.342,20.824C19.342,20.835 19.331,20.846 19.331,20.846C19.32,20.867 19.309,20.889 19.298,20.911C19.287,20.933 19.265,20.966 19.254,20.987C19.244,20.998 19.244,21.009 19.233,21.02C19.211,21.053 19.2,21.075 19.178,21.107C19.178,21.107 19.178,21.108 19.178,21.118C18.687,21.838 17.858,22.318 16.92,22.318C15.404,22.318 14.182,21.097 14.182,19.591Z" /> + <path + android:fillColor="#4E4639" + android:pathData="M12,5.409C12,2.704 9.796,0.5 7.091,0.5C4.385,0.5 2.182,2.704 2.182,5.409V8.322C0.916,8.769 0,9.991 0,11.409C0,13.209 1.473,14.682 3.273,14.682C3.873,14.682 4.462,14.507 4.975,14.202C5.618,14.507 6.338,14.682 7.091,14.682C9.796,14.682 12,12.478 12,9.773V5.409ZM7.091,2.682C8.596,2.682 9.818,3.904 9.818,5.409C9.818,6.915 8.596,8.136 7.091,8.136C5.585,8.136 4.364,6.915 4.364,5.409C4.364,3.904 5.585,2.682 7.091,2.682ZM7.091,12.5C6.153,12.5 5.324,12.02 4.833,11.3C4.833,11.3 4.833,11.3 4.833,11.289C4.811,11.256 4.8,11.234 4.778,11.202C4.767,11.191 4.767,11.18 4.756,11.169C4.745,11.147 4.724,11.115 4.713,11.093C4.702,11.071 4.691,11.049 4.68,11.027C4.68,11.016 4.669,11.005 4.669,11.005C4.647,10.962 4.625,10.918 4.604,10.875C4.516,10.678 4.451,10.482 4.418,10.274C4.418,10.264 4.407,10.242 4.407,10.231C4.407,10.209 4.396,10.176 4.396,10.155C4.396,10.133 4.385,10.1 4.385,10.078C4.385,10.067 4.385,10.045 4.385,10.035C4.385,10.002 4.375,9.958 4.375,9.925C4.375,9.915 4.375,9.904 4.375,9.893C4.364,9.86 4.364,9.816 4.364,9.773V9.489C5.149,10.013 6.087,10.318 7.091,10.318C8.095,10.318 9.033,10.013 9.818,9.489V9.773C9.818,11.278 8.596,12.5 7.091,12.5Z" /> +</vector> + diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index f5bacb62b6b2..c97445f04eea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -230,30 +230,30 @@ public class BluetoothEventManager { @VisibleForTesting void dispatchActiveDeviceChanged( - @Nullable CachedBluetoothDevice activeDevice, - int bluetoothProfile) { + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { + CachedBluetoothDevice targetDevice = activeDevice; for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { - Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); - boolean isActive = Objects.equals(cachedDevice, activeDevice); - if (!isActive && !memberSet.isEmpty()) { - for (CachedBluetoothDevice memberCachedDevice : memberSet) { - isActive = Objects.equals(memberCachedDevice, activeDevice); - if (isActive) { - Log.d(TAG, - "The active device is the member device " - + activeDevice.getDevice().getAnonymizedAddress() - + ". change activeDevice as main device " - + cachedDevice.getDevice().getAnonymizedAddress()); - activeDevice = cachedDevice; - break; - } - } + // should report isActive from main device or it will cause trouble to other callers. + CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); + CachedBluetoothDevice finalTargetDevice = targetDevice; + if (targetDevice != null + && ((subDevice != null && subDevice.equals(targetDevice)) + || cachedDevice.getMemberDevice().stream().anyMatch( + memberDevice -> memberDevice.equals(finalTargetDevice)))) { + Log.d(TAG, + "The active device is the sub/member device " + + targetDevice.getDevice().getAnonymizedAddress() + + ". change targetDevice as main device " + + cachedDevice.getDevice().getAnonymizedAddress()); + targetDevice = cachedDevice; } - cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile); + boolean isActiveDevice = cachedDevice.equals(targetDevice); + cachedDevice.onActiveDeviceChanged(isActiveDevice, bluetoothProfile); mDeviceManager.onActiveDeviceChanged(cachedDevice); } + for (BluetoothCallback callback : mCallbacks) { - callback.onActiveDeviceChanged(activeDevice, bluetoothProfile); + callback.onActiveDeviceChanged(targetDevice, bluetoothProfile); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 8c316d1c4f21..13635c3a8256 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -412,6 +412,21 @@ public class BluetoothEventManagerTest { } @Test + public void dispatchActiveDeviceChanged_activeFromSubDevice_mainCachedDeviceActive() { + CachedBluetoothDevice subDevice = new CachedBluetoothDevice(mContext, mLocalProfileManager, + mDevice3); + mCachedDevice1.setSubDevice(subDevice); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn( + Collections.singletonList(mCachedDevice1)); + mCachedDevice1.onProfileStateChanged(mHearingAidProfile, + BluetoothProfile.STATE_CONNECTED); + + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isFalse(); + mBluetoothEventManager.dispatchActiveDeviceChanged(subDevice, BluetoothProfile.HEARING_AID); + assertThat(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).isTrue(); + } + + @Test public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() { mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java index 21cdc492e4ea..b3d66aa8bc3b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/ActivityTileTest.java @@ -259,8 +259,10 @@ public class ActivityTileTest { } @Test - public void isSearchable_noMetadata_isTrue() { - final Tile tile = new ActivityTile(null, "category"); + public void isSearchable_nullMetadata_isTrue() { + mActivityInfo.metaData = null; + + final Tile tile = new ActivityTile(mActivityInfo, "category"); assertThat(tile.isSearchable()).isTrue(); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 27a45dfc552e..3e0d05cd9ecf 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -391,6 +391,7 @@ public class SettingsHelper { case Settings.Secure.TOUCH_EXPLORATION_ENABLED: case Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED: case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED: + case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED: case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED: return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0; case Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES: diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index d78038ecee61..9ab9ba8bbfed 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -224,6 +224,11 @@ android_library { extra_check_modules: ["SystemUILintChecker"], warning_checks: ["MissingApacheLicenseDetector"], }, + errorprone: { + javacflags: [ + "-Xep:InvalidPatternSyntax:WARN", + ], + }, } filegroup { @@ -548,6 +553,11 @@ android_library { test: true, extra_check_modules: ["SystemUILintChecker"], }, + errorprone: { + javacflags: [ + "-Xep:InvalidPatternSyntax:WARN", + ], + }, } android_app { @@ -589,6 +599,12 @@ android_app { }, plugins: ["dagger2-compiler"], + + errorprone: { + javacflags: [ + "-Xep:InvalidPatternSyntax:WARN", + ], + }, } android_robolectric_test { diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 68d4b6319b2f..a745ab5cbdd9 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -140,4 +140,11 @@ flag { namespace: "systemui" description: "Move LightRevealScrim to recommended architecture" bug: "281655028" -}
\ No newline at end of file +} + +flag { + name: "media_in_scene_container" + namespace: "systemui" + description: "Enable media in the scene container framework" + bug: "296122467" +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index ddd1c67bd5fa..914e5f2c17bf 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -22,7 +22,7 @@ import android.view.View import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.lifecycle.LifecycleOwner -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene @@ -47,6 +47,14 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) { + throwComposeUnavailableError() + } + override fun createFooterActionsView( context: Context, viewModel: FooterActionsViewModel, @@ -67,12 +75,12 @@ object ComposeFacade : BaseComposeFacade { override fun createCommunalView( context: Context, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, ): View { throwComposeUnavailableError() } - override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { + override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View { throwComposeUnavailableError() } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index eeda6c63b68f..59bd95bd9027 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -32,7 +32,7 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalHub -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -62,6 +62,21 @@ object ComposeFacade : BaseComposeFacade { activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } } } + override fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) { + activity.setContent { + PlatformTheme { + CommunalHub( + viewModel = viewModel, + onOpenWidgetPicker = onOpenWidgetPicker, + ) + } + } + } + override fun createFooterActionsView( context: Context, viewModel: FooterActionsViewModel, @@ -98,14 +113,14 @@ object ComposeFacade : BaseComposeFacade { override fun createCommunalView( context: Context, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, ): View { return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } } } } - override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { + override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View { return ComposeView(context).apply { setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 7eb7dacda255..57af2ba59566 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -19,7 +19,6 @@ package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface -import android.content.res.Configuration import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap @@ -54,8 +53,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -68,7 +65,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextOverflow @@ -83,8 +79,8 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf -import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -93,8 +89,8 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.fold.ui.composable.FoldPosture import com.android.systemui.fold.ui.composable.foldPosture +import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -149,10 +145,7 @@ private fun SceneScope.BouncerScene( ) { val backgroundColor = MaterialTheme.colorScheme.surface val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() - val layout = - calculateLayout( - isSideBySideSupported = isSideBySideSupported, - ) + val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box(modifier) { Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) { @@ -163,27 +156,27 @@ private fun SceneScope.BouncerScene( val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible when (layout) { - Layout.STANDARD -> + BouncerSceneLayout.STANDARD -> StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, modifier = childModifier, ) - Layout.SIDE_BY_SIDE -> + BouncerSceneLayout.SIDE_BY_SIDE -> SideBySideLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.STACKED -> + BouncerSceneLayout.STACKED -> StackedLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.SPLIT -> + BouncerSceneLayout.SPLIT -> SplitLayout( viewModel = viewModel, dialogFactory = dialogFactory, @@ -728,58 +721,10 @@ private fun StackedLayout( } } -@Composable -private fun calculateLayout( - isSideBySideSupported: Boolean, -): Layout { - val windowSizeClass = LocalWindowSizeClass.current - val width = windowSizeClass.widthSizeClass - val height = windowSizeClass.heightSizeClass - val isLarge = width > WindowWidthSizeClass.Compact && height > WindowHeightSizeClass.Compact - val isTall = - when (height) { - WindowHeightSizeClass.Expanded -> width < WindowWidthSizeClass.Expanded - WindowHeightSizeClass.Medium -> width < WindowWidthSizeClass.Medium - else -> false - } - val isSquare = - when (width) { - WindowWidthSizeClass.Compact -> height == WindowHeightSizeClass.Compact - WindowWidthSizeClass.Medium -> height == WindowHeightSizeClass.Medium - WindowWidthSizeClass.Expanded -> height == WindowHeightSizeClass.Expanded - else -> false - } - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - - return when { - // Small and tall devices (i.e. phone/folded in portrait) or square device not in landscape - // mode (unfolded with hinge along horizontal plane). - (!isLarge && isTall) || (isSquare && !isLandscape) -> Layout.STANDARD - // Small and wide devices (i.e. phone/folded in landscape). - !isLarge -> Layout.SPLIT - // Large and tall devices (i.e. tablet in portrait). - isTall -> Layout.STACKED - // Large and wide/square devices (i.e. tablet in landscape, unfolded). - else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD - } -} - interface BouncerSceneDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all known adaptive layout configurations. */ -private enum class Layout { - /** The default UI with the bouncer laid out normally. */ - STANDARD, - /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, - /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, - /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, -} - /** Enumerates all supported user-input area visibilities. */ private enum class UserInputAreaVisibility { /** diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt new file mode 100644 index 000000000000..08b7559dcf97 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -0,0 +1,61 @@ +/* + * 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 com.android.systemui.bouncer.ui.composable + +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout +import com.android.systemui.bouncer.ui.helper.SizeClass +import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal + +/** + * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by + * [BouncerSceneLayout.STANDARD]. + */ +@Composable +fun calculateLayout( + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + val windowSizeClass = LocalWindowSizeClass.current + + return calculateLayoutInternal( + width = windowSizeClass.widthSizeClass.toEnum(), + height = windowSizeClass.heightSizeClass.toEnum(), + isSideBySideSupported = isSideBySideSupported, + ) +} + +private fun WindowWidthSizeClass.toEnum(): SizeClass { + return when (this) { + WindowWidthSizeClass.Compact -> SizeClass.COMPACT + WindowWidthSizeClass.Medium -> SizeClass.MEDIUM + WindowWidthSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowWidthSizeClass \"$this\"") + } +} + +private fun WindowHeightSizeClass.toEnum(): SizeClass { + return when (this) { + WindowHeightSizeClass.Compact -> SizeClass.COMPACT + WindowHeightSizeClass.Medium -> SizeClass.MEDIUM + WindowHeightSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowHeightSizeClass \"$this\"") + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 2bbe9b8fc20a..ff1cbd6b04c3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -39,11 +39,11 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf @@ -79,12 +79,6 @@ internal fun PatternBouncer( val lineColor = MaterialTheme.colorScheme.primary val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() } - var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) } - val horizontalSpacing = containerSize.width / colCount - val verticalSpacing = containerSize.height / rowCount - val spacing = min(horizontalSpacing, verticalSpacing).toFloat() - val verticalOffset = containerSize.height - spacing * rowCount - // All dots that should be rendered on the grid. val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState() // The most recently selected dot, if the user is currently dragging. @@ -195,13 +189,14 @@ internal fun PatternBouncer( // This is the position of the input pointer. var inputPosition: Offset? by remember { mutableStateOf(null) } + var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } Canvas( modifier // Need to clip to bounds to make sure that the lines don't follow the input pointer // when it leaves the bounds of the dot grid. .clipToBounds() - .onSizeChanged { containerSize = it } + .onGloballyPositioned { coordinates -> gridCoordinates = coordinates } .thenIf(isInputEnabled) { Modifier.pointerInput(Unit) { awaitEachGesture { @@ -232,62 +227,72 @@ internal fun PatternBouncer( viewModel.onDrag( xPx = change.position.x, yPx = change.position.y, - containerSizePx = containerSize.width, - verticalOffsetPx = verticalOffset, + containerSizePx = size.width, ) } } } ) { - if (isAnimationEnabled) { - // Draw lines between dots. - selectedDots.forEachIndexed { index, dot -> - if (index > 0) { - val previousDot = selectedDots[index - 1] - val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value - val startLerp = 1 - lineFadeOutAnimationProgress - val from = pixelOffset(previousDot, spacing, verticalOffset) - val to = pixelOffset(dot, spacing, verticalOffset) - val lerpedFrom = - Offset( - x = from.x + (to.x - from.x) * startLerp, - y = from.y + (to.y - from.y) * startLerp, + gridCoordinates?.let { nonNullCoordinates -> + val containerSize = nonNullCoordinates.size + val horizontalSpacing = containerSize.width.toFloat() / colCount + val verticalSpacing = containerSize.height.toFloat() / rowCount + val spacing = min(horizontalSpacing, verticalSpacing) + val verticalOffset = containerSize.height - spacing * rowCount + + if (isAnimationEnabled) { + // Draw lines between dots. + selectedDots.forEachIndexed { index, dot -> + if (index > 0) { + val previousDot = selectedDots[index - 1] + val lineFadeOutAnimationProgress = + lineFadeOutAnimatables[previousDot]!!.value + val startLerp = 1 - lineFadeOutAnimationProgress + val from = pixelOffset(previousDot, spacing, verticalOffset) + val to = pixelOffset(dot, spacing, verticalOffset) + val lerpedFrom = + Offset( + x = from.x + (to.x - from.x) * startLerp, + y = from.y + (to.y - from.y) * startLerp, + ) + drawLine( + start = lerpedFrom, + end = to, + cap = StrokeCap.Round, + alpha = lineFadeOutAnimationProgress * lineAlpha(spacing), + color = lineColor, + strokeWidth = lineStrokeWidth, ) - drawLine( - start = lerpedFrom, - end = to, - cap = StrokeCap.Round, - alpha = lineFadeOutAnimationProgress * lineAlpha(spacing), - color = lineColor, - strokeWidth = lineStrokeWidth, - ) + } } - } - // Draw the line between the most recently-selected dot and the input pointer position. - inputPosition?.let { lineEnd -> - currentDot?.let { dot -> - val from = pixelOffset(dot, spacing, verticalOffset) - val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2)) - drawLine( - start = from, - end = lineEnd, - cap = StrokeCap.Round, - alpha = lineAlpha(spacing, lineLength), - color = lineColor, - strokeWidth = lineStrokeWidth, - ) + // Draw the line between the most recently-selected dot and the input pointer + // position. + inputPosition?.let { lineEnd -> + currentDot?.let { dot -> + val from = pixelOffset(dot, spacing, verticalOffset) + val lineLength = + sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2)) + drawLine( + start = from, + end = lineEnd, + cap = StrokeCap.Round, + alpha = lineAlpha(spacing, lineLength), + color = lineColor, + strokeWidth = lineStrokeWidth, + ) + } } } - } - // Draw each dot on the grid. - dots.forEach { dot -> - drawCircle( - center = pixelOffset(dot, spacing, verticalOffset), - color = dotColor, - radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f), - ) + // Draw each dot on the grid. + dots.forEach { dot -> + drawCircle( + center = pixelOffset(dot, spacing, verticalOffset), + color = dotColor, + radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f), + ) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 09706bed1921..2c4dc806f468 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -32,7 +32,7 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalSceneKey -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import kotlinx.coroutines.flow.transform object Communal { @@ -59,16 +59,19 @@ val sceneTransitions = transitions { @Composable fun CommunalContainer( modifier: Modifier = Modifier, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, ) { val currentScene: SceneKey by viewModel.currentScene .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() } .collectAsState(TransitionSceneKey.Blank) + // Don't show hub mode UI if keyguard is present. This is important since we're in the shade, + // which can be opened from many locations. + val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false) // Failsafe to hide the whole SceneTransitionLayout in case of bugginess. var showSceneTransitionLayout by remember { mutableStateOf(true) } - if (!showSceneTransitionLayout) { + if (!showSceneTransitionLayout || !isKeyguardShowing) { return } @@ -129,7 +132,7 @@ private fun BlankScene( /** Scene containing the glanceable hub UI. */ @Composable private fun SceneScope.CommunalScene( - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier, ) { Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } 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 d822d1996eff..e8ecd3a66186 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 @@ -19,6 +19,8 @@ package com.android.systemui.communal.ui.compose import android.os.Bundle import android.util.SizeF import android.widget.FrameLayout +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -31,10 +33,13 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -44,14 +49,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaHostState import com.android.systemui.res.R @@ -59,39 +64,27 @@ import com.android.systemui.res.R @Composable fun CommunalHub( modifier: Modifier = Modifier, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: (() -> Unit)? = null, ) { val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) Box( modifier = modifier.fillMaxSize().background(Color.White), ) { - LazyHorizontalGrid( - modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), - rows = GridCells.Fixed(CommunalContentSize.FULL.span), - contentPadding = PaddingValues(horizontal = Dimensions.Spacing), - horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), - verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), - ) { - items( - count = communalContent.size, - key = { index -> communalContent[index].key }, - span = { index -> GridItemSpan(communalContent[index].size.span) }, - ) { index -> - CommunalContent( - modifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth), - model = communalContent[index], - viewModel = viewModel, - deleteOnClick = viewModel::onDeleteWidget, - size = - SizeF( - Dimensions.CardWidth.value, - communalContent[index].size.dp().value, - ), - ) + CommunalHubLazyGrid( + modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), + communalContent = communalContent, + isEditMode = viewModel.isEditMode, + viewModel = viewModel, + ) + if (viewModel.isEditMode && onOpenWidgetPicker != null) { + IconButton(onClick = onOpenWidgetPicker) { + Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + } + } else { + IconButton(onClick = viewModel::onOpenWidgetEditor) { + Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor)) } - } - IconButton(onClick = viewModel::onOpenWidgetEditor) { - Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor)) } // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving @@ -106,16 +99,80 @@ fun CommunalHub( } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun CommunalHubLazyGrid( + communalContent: List<CommunalContentModel>, + isEditMode: Boolean, + viewModel: BaseCommunalViewModel, + modifier: Modifier = Modifier, +) { + var gridModifier = modifier + val gridState = rememberLazyGridState() + var list = communalContent + var dragDropState: GridDragDropState? = null + if (isEditMode && viewModel is CommunalEditModeViewModel) { + val contentListState = rememberContentListState(communalContent, viewModel) + list = contentListState.list + dragDropState = rememberGridDragDropState(gridState, contentListState) + gridModifier = gridModifier.dragContainer(dragDropState) + } + LazyHorizontalGrid( + modifier = gridModifier, + state = gridState, + rows = GridCells.Fixed(CommunalContentSize.FULL.span), + contentPadding = PaddingValues(horizontal = Dimensions.Spacing), + horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), + verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), + ) { + items( + count = list.size, + key = { index -> list[index].key }, + span = { index -> GridItemSpan(list[index].size.span) }, + ) { index -> + val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth) + val size = + SizeF( + Dimensions.CardWidth.value, + list[index].size.dp().value, + ) + if (isEditMode && dragDropState != null) { + DraggableItem(dragDropState = dragDropState, enabled = true, index = index) { + isDragging -> + val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp) + CommunalContent( + modifier = cardModifier, + deleteOnClick = viewModel::onDeleteWidget, + elevation = elevation, + model = list[index], + viewModel = viewModel, + size = size, + ) + } + } else { + CommunalContent( + modifier = cardModifier, + model = list[index], + viewModel = viewModel, + size = size, + ) + } + } + } +} + @Composable private fun CommunalContent( model: CommunalContentModel, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, size: SizeF, - deleteOnClick: (id: Int) -> Unit, modifier: Modifier = Modifier, + elevation: Dp = 0.dp, + deleteOnClick: ((id: Int) -> Unit)? = null, ) { when (model) { - is CommunalContentModel.Widget -> WidgetContent(model, size, deleteOnClick, modifier) + is CommunalContentModel.Widget -> + WidgetContent(model, size, elevation, deleteOnClick, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, modifier) @@ -126,18 +183,19 @@ private fun CommunalContent( private fun WidgetContent( model: CommunalContentModel.Widget, size: SizeF, - deleteOnClick: (id: Int) -> Unit, + elevation: Dp, + deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { // TODO(b/309009246): update background color - Box( + Card( modifier = modifier.fillMaxSize().background(Color.White), + elevation = CardDefaults.cardElevation(draggedElevation = elevation), ) { - IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { - Icon( - Icons.Default.Close, - LocalContext.current.getString(R.string.button_to_remove_widget) - ) + if (deleteOnClick != null) { + IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { + Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) + } } AndroidView( modifier = modifier, @@ -171,7 +229,7 @@ private fun TutorialContent(modifier: Modifier = Modifier) { } @Composable -private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) { +private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) { AndroidView( modifier = modifier, factory = { @@ -199,7 +257,7 @@ private fun CommunalContentSize.dp(): Dp { } } -private object Dimensions { +object Dimensions { val CardWidth = 464.dp val CardHeightFull = 630.dp val CardHeightHalf = 307.dp 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 new file mode 100644 index 000000000000..89c57658b474 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -0,0 +1,75 @@ +/* + * 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 com.android.systemui.communal.ui.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel + +@Composable +fun rememberContentListState( + communalContent: List<CommunalContentModel>, + viewModel: CommunalEditModeViewModel, +): ContentListState { + return remember(communalContent) { + ContentListState( + communalContent, + viewModel::onDeleteWidget, + viewModel::onReorderWidgets, + ) + } +} + +/** + * Keeps the current state of the [CommunalContentModel] list being edited. [GridDragDropState] + * interacts with this class to update the order in the list. [onSaveList] should be called on + * dragging ends to persist the state in db for better performance. + */ +class ContentListState +internal constructor( + communalContent: List<CommunalContentModel>, + private val onDeleteWidget: (id: Int) -> Unit, + private val onReorderWidgets: (ids: List<Int>) -> Unit, +) { + var list by mutableStateOf(communalContent) + private set + + /** Move item to a new position in the list. */ + fun onMove(fromIndex: Int, toIndex: Int) { + list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) } + } + + /** Remove widget from the list and the database. */ + fun onRemove(indexToRemove: Int) { + if (list[indexToRemove] is CommunalContentModel.Widget) { + val widget = list[indexToRemove] as CommunalContentModel.Widget + list = list.toMutableList().apply { removeAt(indexToRemove) } + onDeleteWidget(widget.appWidgetId) + } + } + + /** Persist the new order with all the movements happened during dragging. */ + fun onSaveList() { + val widgetIds: List<Int> = + list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId } + onReorderWidgets(widgetIds) + } +} 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 new file mode 100644 index 000000000000..6cfa2f233f46 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -0,0 +1,247 @@ +/* + * 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 com.android.systemui.communal.ui.compose + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.lazy.grid.LazyGridItemInfo +import androidx.compose.foundation.lazy.grid.LazyGridItemScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.toOffset +import androidx.compose.ui.unit.toSize +import androidx.compose.ui.zIndex +import com.android.systemui.communal.domain.model.CommunalContentModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch + +@Composable +fun rememberGridDragDropState( + gridState: LazyGridState, + contentListState: ContentListState +): GridDragDropState { + val scope = rememberCoroutineScope() + val state = + remember(gridState, contentListState) { + GridDragDropState(state = gridState, contentListState = contentListState, scope = scope) + } + LaunchedEffect(state) { + while (true) { + val diff = state.scrollChannel.receive() + gridState.scrollBy(diff) + } + } + return state +} + +/** + * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are + * affected will dynamically get positioned and the state is tracked by [ContentListState]. When + * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to + * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the + * change. + */ +class GridDragDropState +internal constructor( + private val state: LazyGridState, + private val contentListState: ContentListState, + private val scope: CoroutineScope, +) { + var draggingItemIndex by mutableStateOf<Int?>(null) + private set + + internal val scrollChannel = Channel<Float>() + + private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) + private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + internal val draggingItemOffset: Offset + get() = + draggingItemLayoutInfo?.let { item -> + draggingItemInitialOffset + draggingItemDraggedDelta - item.offset.toOffset() + } + ?: Offset.Zero + + private val draggingItemLayoutInfo: LazyGridItemInfo? + get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } + + internal fun onDragStart(offset: Offset) { + state.layoutInfo.visibleItemsInfo + .firstOrNull { item -> + item.isEditable && + offset.x.toInt() in item.offset.x..item.offsetEnd.x && + offset.y.toInt() in item.offset.y..item.offsetEnd.y + } + ?.apply { + draggingItemIndex = index + draggingItemInitialOffset = this.offset.toOffset() + } + } + + internal fun onDragInterrupted() { + if (draggingItemIndex != null) { + // persist list editing changes on dragging ends + contentListState.onSaveList() + draggingItemIndex = null + } + draggingItemDraggedDelta = Offset.Zero + draggingItemInitialOffset = Offset.Zero + } + + internal fun onDrag(offset: Offset) { + draggingItemDraggedDelta += offset + + val draggingItem = draggingItemLayoutInfo ?: return + val startOffset = draggingItem.offset.toOffset() + draggingItemOffset + val endOffset = startOffset + draggingItem.size.toSize() + val middleOffset = startOffset + (endOffset - startOffset) / 2f + + val targetItem = + state.layoutInfo.visibleItemsInfo.find { item -> + item.isEditable && + middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x && + middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y && + draggingItem.index != item.index + } + + if (targetItem != null) { + val scrollToIndex = + if (targetItem.index == state.firstVisibleItemIndex) { + draggingItem.index + } else if (draggingItem.index == state.firstVisibleItemIndex) { + targetItem.index + } else { + null + } + if (scrollToIndex != null) { + scope.launch { + // this is needed to neutralize automatic keeping the first item first. + state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset) + contentListState.onMove(draggingItem.index, targetItem.index) + } + } else { + contentListState.onMove(draggingItem.index, targetItem.index) + } + draggingItemIndex = targetItem.index + } else { + val overscroll = checkForOverscroll(startOffset, endOffset) + if (overscroll != 0f) { + scrollChannel.trySend(overscroll) + } + val removeOffset = checkForRemove(startOffset) + if (removeOffset != 0f) { + draggingItemIndex?.let { + contentListState.onRemove(it) + draggingItemIndex = null + } + } + } + } + + private val LazyGridItemInfo.offsetEnd: IntOffset + get() = this.offset + this.size + + /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */ + private val LazyGridItemInfo.isEditable: Boolean + get() = contentListState.list[this.index] is CommunalContentModel.Widget + + /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */ + private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float { + return when { + draggingItemDraggedDelta.x > 0 -> + (endOffset.x - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f) + draggingItemDraggedDelta.x < 0 -> + (startOffset.x - state.layoutInfo.viewportStartOffset).coerceAtMost(0f) + else -> 0f + } + } + + // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up + // and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with + // the Remove button. + private fun checkForRemove(startOffset: Offset): Float { + return if (draggingItemDraggedDelta.y < 0) + (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset) + .coerceAtMost(0f) + else 0f + } +} + +private operator fun IntOffset.plus(size: IntSize): IntOffset { + return IntOffset(x + size.width, y + size.height) +} + +private operator fun Offset.plus(size: Size): Offset { + return Offset(x + size.width, y + size.height) +} + +fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier { + return pointerInput(dragDropState) { + detectDragGesturesAfterLongPress( + onDrag = { change, offset -> + change.consume() + dragDropState.onDrag(offset = offset) + }, + onDragStart = { offset -> dragDropState.onDragStart(offset) }, + onDragEnd = { dragDropState.onDragInterrupted() }, + onDragCancel = { dragDropState.onDragInterrupted() } + ) + } +} + +/** Wrap LazyGrid item with additional modifier needed for drag and drop. */ +@ExperimentalFoundationApi +@Composable +fun LazyGridItemScope.DraggableItem( + dragDropState: GridDragDropState, + index: Int, + enabled: Boolean, + modifier: Modifier = Modifier, + content: @Composable (isDragging: Boolean) -> Unit +) { + if (!enabled) { + return Box(modifier = modifier) { content(false) } + } + val dragging = index == dragDropState.draggingItemIndex + val draggingModifier = + if (dragging) { + Modifier.zIndex(1f).graphicsLayer { + translationX = dragDropState.draggingItemOffset.x + translationY = dragDropState.draggingItemOffset.y + } + } else { + Modifier.animateItemPlacement() + } + Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) { + content(dragging) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt index 1c993cf6206c..e77ade91a93b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -23,19 +23,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker - -sealed interface FoldPosture { - /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ - data object Folded : FoldPosture - /** A foldable that's halfway open with the hinge held vertically. */ - data object Book : FoldPosture - /** A foldable that's halfway open with the hinge held horizontally. */ - data object Tabletop : FoldPosture - /** A foldable that's fully unfolded / flat. */ - data object FullyUnfolded : FoldPosture -} +import com.android.systemui.fold.ui.helper.FoldPosture +import com.android.systemui.fold.ui.helper.foldPostureInternal /** Returns the [FoldPosture] of the device currently. */ @Composable @@ -48,32 +38,6 @@ fun foldPosture(): State<FoldPosture> { initialValue = FoldPosture.Folded, key1 = layoutInfo, ) { - value = - layoutInfo - ?.displayFeatures - ?.firstNotNullOfOrNull { it as? FoldingFeature } - .let { foldingFeature -> - when (foldingFeature?.state) { - null -> FoldPosture.Folded - FoldingFeature.State.HALF_OPENED -> - foldingFeature.orientation.toHalfwayPosture() - FoldingFeature.State.FLAT -> - if (foldingFeature.isSeparating) { - // Dual screen device. - foldingFeature.orientation.toHalfwayPosture() - } else { - FoldPosture.FullyUnfolded - } - else -> error("Unsupported state \"${foldingFeature.state}\"") - } - } - } -} - -private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { - return when (this) { - FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop - FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book - else -> error("Unsupported orientation \"$this\"") + value = foldPostureInternal(layoutInfo) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 28a4801d582a..765468372604 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -25,9 +25,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -36,7 +33,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -76,9 +72,6 @@ fun SceneScope.QuickSettings( .element(QuickSettings.Elements.Content) .fillMaxWidth() .defaultMinSize(minHeight = 300.dp) - .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.primary) - .padding(1.dp), ) { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 9dd7bfaf3549..871d9f9b7627 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -23,7 +23,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.clickable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -51,6 +52,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader +import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager @@ -104,53 +106,59 @@ private fun SceneScope.QuickSettingsScene( ) { // TODO(b/280887232): implement the real UI. Box(modifier = modifier.fillMaxSize()) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding(start = 16.dp, end = 16.dp, bottom = 48.dp) - ) { - when (LocalWindowSizeClass.current.widthSizeClass) { - WindowWidthSizeClass.Compact -> - AnimatedVisibility( - visible = !isCustomizing, - enter = - expandVertically( - animationSpec = tween(1000), - initialHeight = { collapsedHeaderHeight }, - ) + fadeIn(tween(1000)), - exit = - shrinkVertically( - animationSpec = tween(1000), - targetHeight = { collapsedHeaderHeight }, - shrinkTowards = Alignment.Top, - ) + fadeOut(tween(1000)), - ) { - ExpandedShadeHeader( + Box(modifier = Modifier.fillMaxSize()) { + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } + Spacer( + modifier = + Modifier.element(Shade.Elements.ScrimBackground) + .fillMaxSize() + .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp) + ) { + when (LocalWindowSizeClass.current.widthSizeClass) { + WindowWidthSizeClass.Compact -> + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(1000), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(1000)), + exit = + shrinkVertically( + animationSpec = tween(1000), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(1000)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + ) + } + else -> + CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, ) - } - else -> - CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + } + Spacer(modifier = Modifier.height(16.dp)) + QuickSettings( + modifier = Modifier.fillMaxHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QS + ) } - Spacer(modifier = Modifier.height(16.dp)) - QuickSettings( - modifier = Modifier.fillMaxHeight(), - viewModel.qsSceneAdapter, - QSSceneAdapter.State.QS - ) } HeadsUpNotificationSpace( viewModel = viewModel.notifications, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index c51287a7bb71..b00c88612269 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import com.android.compose.nestedscroll.PriorityNestedScrollConnection @@ -536,24 +535,6 @@ internal class SceneNestedScrollHandler( ) : NestedScrollHandler { override val connection: PriorityNestedScrollConnection = nestedScrollConnection() - private fun Offset.toAmount() = - when (gestureHandler.orientation) { - Orientation.Horizontal -> x - Orientation.Vertical -> y - } - - private fun Velocity.toAmount() = - when (gestureHandler.orientation) { - Orientation.Horizontal -> x - Orientation.Vertical -> y - } - - private fun Float.toOffset() = - when (gestureHandler.orientation) { - Orientation.Horizontal -> Offset(x = this, y = 0f) - Orientation.Vertical -> Offset(x = 0f, y = this) - } - 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. @@ -591,13 +572,12 @@ internal class SceneNestedScrollHandler( } return PriorityNestedScrollConnection( + orientation = gestureHandler.orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart -> - canChangeScene = offsetBeforeStart == Offset.Zero + canChangeScene = offsetBeforeStart == 0f val canInterceptSwipeTransition = - canChangeScene && - gestureHandler.isDrivingTransition && - offsetAvailable.toAmount() != 0f + canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false val progress = gestureHandler.swipeTransition.progress @@ -618,15 +598,14 @@ internal class SceneNestedScrollHandler( !shouldSnapToIdle }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> - val amount = offsetAvailable.toAmount() val behavior: NestedScrollBehavior = when { - amount > 0 -> startBehavior - amount < 0 -> endBehavior + offsetAvailable > 0f -> startBehavior + offsetAvailable < 0f -> endBehavior else -> return@PriorityNestedScrollConnection false } - val isZeroOffset = offsetBeforeStart == Offset.Zero + val isZeroOffset = offsetBeforeStart == 0f when (behavior) { NestedScrollBehavior.DuringTransitionBetweenScenes -> { @@ -635,30 +614,29 @@ internal class SceneNestedScrollHandler( } NestedScrollBehavior.EdgeNoOverscroll -> { canChangeScene = isZeroOffset - isZeroOffset && hasNextScene(amount) + isZeroOffset && hasNextScene(offsetAvailable) } NestedScrollBehavior.EdgeWithOverscroll -> { canChangeScene = isZeroOffset - hasNextScene(amount) + hasNextScene(offsetAvailable) } NestedScrollBehavior.Always -> { canChangeScene = true - hasNextScene(amount) + hasNextScene(offsetAvailable) } } }, canStartPostFling = { velocityAvailable -> - val amount = velocityAvailable.toAmount() val behavior: NestedScrollBehavior = when { - amount > 0 -> startBehavior - amount < 0 -> endBehavior + velocityAvailable > 0f -> startBehavior + velocityAvailable < 0f -> endBehavior else -> return@PriorityNestedScrollConnection false } // We could start an overscroll animation canChangeScene = false - behavior.canStartOnPostFling && hasNextScene(amount) + behavior.canStartOnPostFling && hasNextScene(velocityAvailable) }, canContinueScroll = { true }, onStart = { @@ -671,24 +649,22 @@ internal class SceneNestedScrollHandler( }, onScroll = { offsetAvailable -> if (gestureHandler.gestureWithPriority != this) { - return@PriorityNestedScrollConnection Offset.Zero + return@PriorityNestedScrollConnection 0f } - val amount = offsetAvailable.toAmount() - // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. - gestureHandler.onDrag(amount) + gestureHandler.onDrag(offsetAvailable) - amount.toOffset() + offsetAvailable }, onStop = { velocityAvailable -> if (gestureHandler.gestureWithPriority != this) { - return@PriorityNestedScrollConnection Velocity.Zero + return@PriorityNestedScrollConnection 0f } gestureHandler.onDragStopped( - velocity = velocityAvailable.toAmount(), + velocity = velocityAvailable, canChangeScene = canChangeScene ) 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 824c10b88a9b..a5fd1bfb72e6 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 @@ -16,10 +16,12 @@ package com.android.compose.nestedscroll +import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity +import com.android.compose.ui.util.SpaceVectorConverter /** * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and @@ -147,3 +149,35 @@ class PriorityNestedScrollConnection( return onStop(velocity) } } + +fun PriorityNestedScrollConnection( + orientation: Orientation, + canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, + canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, + canStartPostFling: (velocityAvailable: Float) -> Boolean, + canContinueScroll: () -> Boolean, + onStart: () -> Unit, + onScroll: (offsetAvailable: Float) -> Float, + onStop: (velocityAvailable: Float) -> Float, +) = + with(SpaceVectorConverter(orientation)) { + PriorityNestedScrollConnection( + canStartPreScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset -> + canStartPreScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat()) + }, + canStartPostScroll = { offsetAvailable: Offset, offsetBeforeStart: Offset -> + canStartPostScroll(offsetAvailable.toFloat(), offsetBeforeStart.toFloat()) + }, + canStartPostFling = { velocityAvailable: Velocity -> + canStartPostFling(velocityAvailable.toFloat()) + }, + canContinueScroll = canContinueScroll, + onStart = onStart, + onScroll = { offsetAvailable: Offset -> + onScroll(offsetAvailable.toFloat()).toOffset() + }, + onStop = { velocityAvailable: Velocity -> + onStop(velocityAvailable.toFloat()).toVelocity() + }, + ) + } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt new file mode 100644 index 000000000000..a13e9441523a --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt @@ -0,0 +1,50 @@ +/* + * 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 com.android.compose.ui.util + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Velocity + +interface SpaceVectorConverter { + fun Offset.toFloat(): Float + fun Velocity.toFloat(): Float + fun Float.toOffset(): Offset + fun Float.toVelocity(): Velocity +} + +fun SpaceVectorConverter(orientation: Orientation) = + when (orientation) { + Orientation.Horizontal -> HorizontalConverter + Orientation.Vertical -> VerticalConverter + } + +private val HorizontalConverter = + object : SpaceVectorConverter { + override fun Offset.toFloat() = x + override fun Velocity.toFloat() = x + override fun Float.toOffset() = Offset(this, 0f) + override fun Float.toVelocity() = Velocity(this, 0f) + } + +private val VerticalConverter = + object : SpaceVectorConverter { + override fun Offset.toFloat() = y + override fun Velocity.toFloat() = y + override fun Float.toOffset() = Offset(0f, this) + override fun Float.toVelocity() = Velocity(0f, this) + } 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 new file mode 100644 index 000000000000..ce7db80db7da --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -0,0 +1,126 @@ +/* + * 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 com.android.systemui.communal.view.viewmodel + +import android.app.smartspace.SmartspaceTarget +import android.provider.Settings +import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +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.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalEditModeViewModelTest : SysuiTestCase() { + @Mock private lateinit var mediaHost: MediaHost + + private lateinit var testScope: TestScope + + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var tutorialRepository: FakeCommunalTutorialRepository + private lateinit var widgetRepository: FakeCommunalWidgetRepository + private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var mediaRepository: FakeCommunalMediaRepository + + private lateinit var underTest: CommunalEditModeViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + + val withDeps = CommunalInteractorFactory.create() + keyguardRepository = withDeps.keyguardRepository + communalRepository = withDeps.communalRepository + tutorialRepository = withDeps.tutorialRepository + widgetRepository = withDeps.widgetRepository + smartspaceRepository = withDeps.smartspaceRepository + mediaRepository = withDeps.mediaRepository + + underTest = + CommunalEditModeViewModel( + withDeps.communalInteractor, + mediaHost, + ) + } + + @Test + fun communalContent_onlyWidgetsAreShownInEditMode() = + testScope.runTest { + tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + + // Widgets available. + val widgets = + listOf( + CommunalWidgetContentModel( + appWidgetId = 0, + priority = 30, + providerInfo = mock(), + ), + CommunalWidgetContentModel( + appWidgetId = 1, + priority = 20, + providerInfo = mock(), + ), + ) + widgetRepository.setCommunalWidgets(widgets) + + // Smartspace available. + val target = Mockito.mock(SmartspaceTarget::class.java) + whenever(target.smartspaceTargetId).thenReturn("target") + whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) + whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) + smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + + // Media playing. + mediaRepository.mediaPlaying.value = true + + val communalContent by collectLastValue(underTest.communalContent) + + // Only Widgets are shown. + assertThat(communalContent?.size).isEqualTo(2) + assertThat(communalContent?.get(0)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + assertThat(communalContent?.get(1)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + } +} 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 new file mode 100644 index 000000000000..32f4d075a873 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -0,0 +1,148 @@ +/* + * 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 com.android.systemui.communal.view.viewmodel + +import android.app.smartspace.SmartspaceTarget +import android.provider.Settings +import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository +import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository +import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +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.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalViewModelTest : SysuiTestCase() { + @Mock private lateinit var mediaHost: MediaHost + + private lateinit var testScope: TestScope + + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var tutorialRepository: FakeCommunalTutorialRepository + private lateinit var widgetRepository: FakeCommunalWidgetRepository + private lateinit var smartspaceRepository: FakeSmartspaceRepository + private lateinit var mediaRepository: FakeCommunalMediaRepository + + private lateinit var underTest: CommunalViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + + val withDeps = CommunalInteractorFactory.create() + keyguardRepository = withDeps.keyguardRepository + communalRepository = withDeps.communalRepository + tutorialRepository = withDeps.tutorialRepository + widgetRepository = withDeps.widgetRepository + smartspaceRepository = withDeps.smartspaceRepository + mediaRepository = withDeps.mediaRepository + + underTest = + CommunalViewModel( + withDeps.communalInteractor, + withDeps.tutorialInteractor, + mediaHost, + ) + } + + @Test + fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() = + testScope.runTest { + // Keyguard showing, and tutorial not started. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState( + Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED + ) + + val communalContent by collectLastValue(underTest.communalContent) + + assertThat(communalContent!!).isNotEmpty() + communalContent!!.forEach { model -> + assertThat(model is CommunalContentModel.Tutorial).isTrue() + } + } + + @Test + fun ordering_smartspaceBeforeUmoBeforeWidgets() = + testScope.runTest { + tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + + // Widgets available. + val widgets = + listOf( + CommunalWidgetContentModel( + appWidgetId = 0, + priority = 30, + providerInfo = mock(), + ), + CommunalWidgetContentModel( + appWidgetId = 1, + priority = 20, + providerInfo = mock(), + ), + ) + widgetRepository.setCommunalWidgets(widgets) + + // Smartspace available. + val target = Mockito.mock(SmartspaceTarget::class.java) + whenever(target.smartspaceTargetId).thenReturn("target") + whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) + whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) + smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + + // Media playing. + mediaRepository.mediaPlaying.value = true + + val communalContent by collectLastValue(underTest.communalContent) + + // Order is smart space, then UMO, then widget content. + assertThat(communalContent?.size).isEqualTo(4) + assertThat(communalContent?.get(0)) + .isInstanceOf(CommunalContentModel.Smartspace::class.java) + assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java) + assertThat(communalContent?.get(2)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + assertThat(communalContent?.get(3)) + .isInstanceOf(CommunalContentModel.Widget::class.java) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt new file mode 100644 index 000000000000..61b205710873 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt @@ -0,0 +1,124 @@ +/* + * 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 com.android.systemui.fold.ui.helper + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FoldPostureTest : SysuiTestCase() { + + @Test + fun foldPosture_whenNull_returnsFolded() { + assertThat(foldPostureInternal(null)).isEqualTo(FoldPosture.Folded) + } + + @Test + fun foldPosture_whenHalfOpenHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenHalfOpenVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + @Test + fun foldPosture_whenFlatAndNotSeparating_returnsFullyUnfolded() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + orientation = FoldingFeature.Orientation.HORIZONTAL, + isSeparating = false, + ) + ) + ) + .isEqualTo(FoldPosture.FullyUnfolded) + } + + @Test + fun foldPosture_whenFlatAndSeparatingHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenFlatAndSeparatingVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + private fun createWindowLayoutInfo( + state: FoldingFeature.State, + orientation: FoldingFeature.Orientation = FoldingFeature.Orientation.VERTICAL, + isSeparating: Boolean = false, + occlusionType: FoldingFeature.OcclusionType = FoldingFeature.OcclusionType.NONE, + ): WindowLayoutInfo { + return WindowLayoutInfo( + listOf( + object : FoldingFeature { + override val bounds: Rect = Rect(0, 0, 100, 100) + override val isSeparating: Boolean = isSeparating + override val occlusionType: FoldingFeature.OcclusionType = occlusionType + override val orientation: FoldingFeature.Orientation = orientation + override val state: FoldingFeature.State = state + } + ) + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt new file mode 100644 index 000000000000..fab290da2953 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -0,0 +1,97 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight.domain + +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FlashlightMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileConfig = kosmos.qsFlashlightTileConfig + private val mapper by lazy { FlashlightMapper(context) } + + @Test + fun mapsDisabledDataToInactiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false)) + + val actualActivationState = tileState.activationState + + assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToActiveState() { + val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true)) + + val actualActivationState = tileState.activationState + assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState) + } + + @Test + fun mapsEnabledDataToOnIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride( + R.drawable.qs_flashlight_icon_on, + fakeDrawable + ) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true)) + + val actualIcon = tileState.icon() + assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun mapsDisabledDataToOffIconState() { + val fakeDrawable = mock<Drawable>() + context.orCreateTestableResources.addOverride( + R.drawable.qs_flashlight_icon_off, + fakeDrawable + ) + val expectedIcon = Icon.Loaded(fakeDrawable, null) + + val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false)) + + val actualIcon = tileState.icon() + assertThat(actualIcon).isEqualTo(expectedIcon) + } + + @Test + fun supportsOnlyClickAction() { + val dontCare = true + val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare)) + + val supportedActions = tileState.supportedActions + assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt new file mode 100644 index 000000000000..00572d3163eb --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt @@ -0,0 +1,88 @@ +/* + * 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. + */ + +import android.os.UserHandle +import android.testing.LeakCheck +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.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.utils.leaks.FakeFlashlightController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class FlashlightTileDataInteractorTest : SysuiTestCase() { + private lateinit var controller: FakeFlashlightController + private lateinit var underTest: FlashlightTileDataInteractor + + @Before + fun setup() { + controller = FakeFlashlightController(LeakCheck()) + underTest = FlashlightTileDataInteractor(controller) + } + + @Test + fun availabilityOnMatchesController() = runTest { + controller.hasFlashlight = true + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + assertThat(availability).isTrue() + } + @Test + fun availabilityOffMatchesController() = runTest { + controller.hasFlashlight = false + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + assertThat(availability).isFalse() + } + + @Test + fun dataMatchesController() = runTest { + controller.setFlashlight(false) + val flowValues: List<FlashlightTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + controller.setFlashlight(true) + runCurrent() + controller.setFlashlight(false) + runCurrent() + + assertThat(flowValues.size).isEqualTo(3) + assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..f819f53838b8 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import android.app.ActivityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click +import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.statusbar.policy.FlashlightController +import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.test.runTest +import org.junit.Assume.assumeFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FlashlightTileUserActionInteractorTest : SysuiTestCase() { + + @Mock private lateinit var controller: FlashlightController + + private lateinit var underTest: FlashlightTileUserActionInteractor + + @Before + fun setup() { + controller = mock<FlashlightController>() + underTest = FlashlightTileUserActionInteractor(controller) + } + + @Test + fun handleClickToEnable() = runTest { + assumeFalse(ActivityManager.isUserAMonkey()) + val stateBeforeClick = false + + underTest.handleInput(click(FlashlightTileModel(stateBeforeClick))) + + verify(controller).setFlashlight(!stateBeforeClick) + } + + @Test + fun handleClickToDisable() = runTest { + assumeFalse(ActivityManager.isUserAMonkey()) + val stateBeforeClick = true + + underTest.handleInput(click(FlashlightTileModel(stateBeforeClick))) + + verify(controller).setFlashlight(!stateBeforeClick) + } +} diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml deleted file mode 100644 index bc1775ee64ae..000000000000 --- a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@id/background" - android:gravity="center"> - <shape android:shape="oval"> - <size - android:height="24px" - android:width="24px" - /> - <solid android:color="#FFFFFFFF" /> - </shape> - </item> - <item android:id="@id/icon" - android:gravity="center" - android:width="20px" - android:height="20px" - android:drawable="@drawable/ic_person_outline" - /> -</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml new file mode 100644 index 000000000000..a1e8b9d0443d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_assistant_attention_indicator.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <group> + <path + android:pathData="M12 8.13333C12.7089 8.13333 13.2889 8.71333 13.2889 9.42221C13.2889 10.1311 12.7089 10.7111 12 10.7111C11.2911 10.7111 10.7111 10.1311 10.7111 9.42221C10.7111 8.71333 11.2911 8.13333 12 8.13333Z" + android:fillColor="#FFFFFF" + /> + <path + android:pathData="M12 13.9333C13.74 13.9333 15.7378 14.7647 15.8667 15.2222V15.8667H8.13333V15.2287C8.26221 14.7647 10.26 13.9333 12 13.9333Z" + android:fillColor="#FFFFFF" + /> + <path + android:pathData="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM9.42228 9.42224C9.42228 7.99802 10.5758 6.84447 12.0001 6.84447C13.4243 6.84447 14.5778 7.99802 14.5778 9.42224C14.5778 10.8465 13.4243 12 12.0001 12C10.5758 12 9.42228 10.8465 9.42228 9.42224ZM12 12.6445C10.2794 12.6445 6.84447 13.508 6.84447 15.2223V17.1556H17.1556V15.2223C17.1556 13.508 13.7207 12.6445 12 12.6445Z" + android:fillColor="#FFFFFF" + android:fillType="evenOdd" + /> + </group> +</vector> + + diff --git a/packages/SystemUI/res/drawable/ic_person_outline.xml b/packages/SystemUI/res/drawable/ic_person_outline.xml deleted file mode 100644 index d94714e0d51a..000000000000 --- a/packages/SystemUI/res/drawable/ic_person_outline.xml +++ /dev/null @@ -1,26 +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. - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> - <path - android:fillColor="@android:color/black" - android:pathData="M480,480Q414,480 367,433Q320,386 320,320Q320,254 367,207Q414,160 480,160Q546,160 593,207Q640,254 640,320Q640,386 593,433Q546,480 480,480ZM160,800L160,688Q160,654 177.5,625.5Q195,597 224,582Q286,551 350,535.5Q414,520 480,520Q546,520 610,535.5Q674,551 736,582Q765,597 782.5,625.5Q800,654 800,688L800,800L160,800ZM240,720L720,720L720,688Q720,677 714.5,668Q709,659 700,654Q646,627 591,613.5Q536,600 480,600Q424,600 369,613.5Q314,627 260,654Q251,659 245.5,668Q240,677 240,688L240,720ZM480,400Q513,400 536.5,376.5Q560,353 560,320Q560,287 536.5,263.5Q513,240 480,240Q447,240 423.5,263.5Q400,287 400,320Q400,353 423.5,376.5Q447,400 480,400ZM480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320ZM480,720L480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720L480,720L480,720Z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml index 34e7d0afcd97..f7c04b5366f8 100644 --- a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml +++ b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml @@ -26,10 +26,17 @@ </shape> </item> <item - android:drawable="@drawable/ic_ksh_key_down" - android:gravity="end|center_vertical" - android:textColor="?androidprv:attr/textColorPrimary" - android:width="@dimen/screenrecord_spinner_arrow_size" - android:height="@dimen/screenrecord_spinner_arrow_size" - android:end="20dp" /> + android:end="20dp" + android:gravity="end|center_vertical"> + <vector + android:width="@dimen/screenrecord_spinner_arrow_size" + android:height="@dimen/screenrecord_spinner_arrow_size" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?androidprv:attr/colorControlNormal"> + <path + android:fillColor="#FF000000" + android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" /> + </vector> + </item> </layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index ad9a775ed243..ec2edb52a039 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -113,7 +113,7 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" - android:src="@drawable/dream_overlay_assistant_attention_indicator" + android:src="@drawable/ic_assistant_attention_indicator" android:visibility="gone" android:contentDescription="@string/assistant_attention_content_description" /> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index b8f4c0f212c3..7ab44e70e6fe 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -53,6 +53,8 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:focusable="true" + android:importantForAccessibility="no" android:tint="?attr/shadeActive" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/edit_widgets.xml b/packages/SystemUI/res/layout/udfps_touch_overlay.xml index 182e651aa66d..ea92776aba2d 100644 --- a/packages/SystemUI/res/layout/edit_widgets.xml +++ b/packages/SystemUI/res/layout/udfps_touch_overlay.xml @@ -1,3 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2023 The Android Open Source Project ~ @@ -13,20 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/edit_widgets" +<com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_touch_overlay" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <Button - style="@android:Widget.DeviceDefault.Button.Colored" - android:id="@+id/add_widget" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:textSize="28sp" - android:text="@string/hub_mode_add_widget_button_text"/> - -</FrameLayout> + android:layout_height="match_parent" + android:contentDescription="@string/accessibility_fingerprint_label"> +</com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 74d435d18823..1838795a57d6 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -230,7 +230,6 @@ <!-- Communal mode --> <item type="id" name="communal_hub" /> - <item type="id" name="communal_widget_wrapper" /> <!-- Values assigned to the views in Biometrics Prompt --> <item type="id" name="pin_pad"/> diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 9305ab6f6968..7ccf70427327 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -7,6 +7,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.SearchManager; +import android.app.StatusBarManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -439,6 +440,14 @@ public class AssistManager { public void onStartPerceiving() { mAssistUtils.enableVisualQueryDetection( mVisualQueryDetectionAttentionListener); + final StatusBarManager statusBarManager = + mContext.getSystemService(StatusBarManager.class); + if (statusBarManager != null) { + statusBarManager.setIcon("assist_attention", + R.drawable.ic_assistant_attention_indicator, + 0, "Attention Icon for Assistant"); + statusBarManager.setIconVisibility("assist_attention", false); + } } @Override @@ -447,11 +456,20 @@ public class AssistManager { // accordingly). handleVisualAttentionChanged(false); mAssistUtils.disableVisualQueryDetection(); + final StatusBarManager statusBarManager = + mContext.getSystemService(StatusBarManager.class); + if (statusBarManager != null) { + statusBarManager.removeIcon("assist_attention"); + } } }); } private void handleVisualAttentionChanged(boolean attentionGained) { + final StatusBarManager statusBarManager = mContext.getSystemService(StatusBarManager.class); + if (statusBarManager != null) { + statusBarManager.setIconVisibility("assist_attention", attentionGained); + } mVisualQueryAttentionListeners.forEach( attentionGained ? VisualQueryAttentionListener::onAttentionGained diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 57e252dd9929..8fe42b536b1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -100,7 +100,6 @@ import javax.inject.Inject; import javax.inject.Provider; import kotlin.Unit; - import kotlinx.coroutines.CoroutineScope; /** @@ -1099,6 +1098,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT + || error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index b064391f74b5..e15538b88d8c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -52,6 +52,7 @@ import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -78,9 +79,9 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -150,7 +151,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; - @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @@ -281,7 +281,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { fromUdfpsView ), mActivityLaunchAnimator, - mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, @@ -318,10 +317,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { return; } mAcquiredReceived = true; - final UdfpsView view = mOverlay.getOverlayView(); - if (view != null && isOptical()) { - unconfigureDisplay(view); - } + final View view = mOverlay.getTouchOverlay(); + unconfigureDisplay(view); tryAodSendFingerUp(); }); } @@ -339,7 +336,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay == null || mOverlay.isHiding()) { return; } - mOverlay.getOverlayView().setDebugMessage(message); + if (!DeviceEntryUdfpsRefactor.isEnabled()) { + ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message); + } }); } @@ -506,6 +505,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f && !mAlternateBouncerInteractor.isVisibleState()) || mPrimaryBouncerInteractor.isInTransit()) { + Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor"); return false; } if (event.getAction() == MotionEvent.ACTION_DOWN @@ -563,7 +563,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } mAttemptedToDismissKeyguard = false; onFingerUp(requestId, - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), data.getPointerId(), data.getX(), data.getY(), @@ -597,7 +597,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (shouldPilfer && !mPointerPilfered && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) { mInputManager.pilferPointers( - mOverlay.getOverlayView().getViewRootImpl().getInputToken()); + mOverlay.getTouchOverlay().getViewRootImpl().getInputToken()); mPointerPilfered = true; } @@ -605,9 +605,15 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private boolean shouldTryToDismissKeyguard() { - return mOverlay != null - && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerAdapter + boolean onKeyguard = false; + if (DeviceEntryUdfpsRefactor.isEnabled()) { + onKeyguard = mKeyguardStateController.isShowing(); + } else { + onKeyguard = mOverlay != null + && mOverlay.getAnimationViewController() + instanceof UdfpsKeyguardViewControllerAdapter; + } + return onKeyguard && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -623,7 +629,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull FeatureFlags featureFlags, @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, @@ -670,7 +675,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mDumpManager = dumpManager; mDialogManager = dialogManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mFeatureFlags = featureFlags; mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; @@ -737,9 +741,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting public void playStartHaptic() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - if (mOverlay != null && mOverlay.getOverlayView() != null) { + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { mVibrator.performHapticFeedback( - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), HapticFeedbackConstants.CONTEXT_CLICK ); } else { @@ -751,10 +755,11 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dozeTimeTick() { - if (mOverlay != null) { - final UdfpsView view = mOverlay.getOverlayView(); + if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) { + DeviceEntryUdfpsRefactor.assertInLegacyMode(); + final View view = mOverlay.getTouchOverlay(); if (view != null) { - view.dozeTimeTick(); + ((UdfpsView) view).dozeTimeTick(); } } } @@ -797,7 +802,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay != null) { // Reset the controller back to its starting state. - final UdfpsView oldView = mOverlay.getOverlayView(); + final View oldView = mOverlay.getTouchOverlay(); if (oldView != null) { onFingerUp(mOverlay.getRequestId(), oldView); } @@ -813,9 +818,21 @@ public class UdfpsController implements DozeReceiver, Dumpable { } - private void unconfigureDisplay(@NonNull UdfpsView view) { - if (view.isDisplayConfigured()) { - view.unconfigureDisplay(); + private void unconfigureDisplay(View view) { + if (!isOptical()) { + return; + } + if (DeviceEntryUdfpsRefactor.isEnabled()) { + if (mUdfpsDisplayMode != null) { + mUdfpsDisplayMode.disable(null); // beverlt + } + } else { + if (view != null) { + UdfpsView udfpsView = (UdfpsView) view; + if (udfpsView.isDisplayConfigured()) { + udfpsView.unconfigureDisplay(); + } + } } } @@ -837,10 +854,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { } mKeyguardViewManager.showPrimaryBouncer(true); - // play the same haptic as the LockIconViewController longpress - if (mOverlay != null && mOverlay.getOverlayView() != null) { + // play the same haptic as the DeviceEntryIcon longpress + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { mVibrator.performHapticFeedback( - mOverlay.getOverlayView(), + mOverlay.getTouchOverlay(), UdfpsController.LONG_PRESS ); } else { @@ -909,8 +926,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { return; } cancelAodSendFingerUpAction(); - if (mOverlay != null && mOverlay.getOverlayView() != null) { - onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView()); + if (mOverlay != null && mOverlay.getTouchOverlay() != null) { + onFingerUp(mOverlay.getRequestId(), mOverlay.getTouchOverlay()); } } @@ -1004,12 +1021,17 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, minor, major, orientation, time, gestureStart, isAod); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); - final UdfpsView view = mOverlay.getOverlayView(); + + final View view = mOverlay.getTouchOverlay(); if (view != null && isOptical()) { if (mIgnoreRefreshRate) { dispatchOnUiReady(requestId); } else { - view.configureDisplay(() -> dispatchOnUiReady(requestId)); + if (DeviceEntryUdfpsRefactor.isEnabled()) { + mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId)); + } else { + ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId)); + } } } @@ -1018,7 +1040,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private void onFingerUp(long requestId, @NonNull UdfpsView view) { + private void onFingerUp(long requestId, @NonNull View view) { onFingerUp( requestId, view, @@ -1035,7 +1057,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { private void onFingerUp( long requestId, - @NonNull UdfpsView view, + View view, int pointerId, float x, float y, @@ -1056,9 +1078,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } mOnFingerDown = false; - if (isOptical()) { - unconfigureDisplay(view); - } + unconfigureDisplay(view); cancelAodSendFingerUpAction(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index a5bd89a15e5a..2d54f7ac8e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,11 +46,11 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -96,7 +96,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, private val activityLaunchAnimator: ActivityLaunchAnimator, - private val featureFlags: FeatureFlags, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, @@ -104,9 +103,22 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val transitionInteractor: KeyguardTransitionInteractor, private val selectedUserInteractor: SelectedUserInteractor, ) { - /** The view, when [isShowing], or null. */ - var overlayView: UdfpsView? = null + private var overlayViewLegacy: UdfpsView? = null private set + private var overlayTouchView: UdfpsTouchOverlay? = null + + /** + * Get the current UDFPS overlay touch view which is a different View depending on whether + * the DeviceEntryUdfpsRefactor flag is enabled or not. + * @return The view, when [isShowing], else null + */ + fun getTouchOverlay(): View? { + return if (DeviceEntryUdfpsRefactor.isEnabled) { + overlayTouchView + } else { + overlayViewLegacy + } + } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() private var sensorBounds: Rect = Rect() @@ -132,15 +144,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor( /** If the overlay is currently showing. */ val isShowing: Boolean - get() = overlayView != null + get() = getTouchOverlay() != null /** Opposite of [isShowing]. */ val isHiding: Boolean - get() = overlayView == null + get() = getTouchOverlay() == null /** The animation controller if the overlay [isShowing]. */ val animationViewController: UdfpsAnimationViewController<*>? - get() = overlayView?.animationViewController + get() = overlayViewLegacy?.animationViewController private var touchExplorationEnabled = false @@ -158,28 +170,48 @@ class UdfpsControllerOverlay @JvmOverloads constructor( /** Show the overlay or return false and do nothing if it is already showing. */ @SuppressLint("ClickableViewAccessibility") fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean { - if (overlayView == null) { + if (getTouchOverlay() == null) { overlayParams = params sensorBounds = Rect(params.sensorBounds) try { - overlayView = (inflater.inflate( - R.layout.udfps_view, null, false - ) as UdfpsView).apply { - overlayParams = params - setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) - val animation = inflateUdfpsAnimation(this, controller) - if (animation != null) { - animation.init() - animationViewController = animation - } - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + if (DeviceEntryUdfpsRefactor.isEnabled) { + overlayTouchView = (inflater.inflate( + R.layout.udfps_touch_overlay, null, false + ) as UdfpsTouchOverlay).apply { + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + windowManager.addView(this, coreLayoutParams.updateDimensions(null)) } + // TODO (b/305234447): Bind view model to UdfpsTouchOverlay here to control + // the visibility (sometimes, even if UDFPS is running, the UDFPS UI can be + // obscured and we don't want to accept touches. ie: for enrollment don't accept + // touches when the shade is expanded and for keyguard: don't accept touches + // depending on the keyguard & shade state + } else { + overlayViewLegacy = (inflater.inflate( + R.layout.udfps_view, null, false + ) as UdfpsView).apply { + overlayParams = params + setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) + val animation = inflateUdfpsAnimation(this, controller) + if (animation != null) { + animation.init() + animationViewController = animation + } + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } - windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) - sensorRect = sensorBounds + windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) + sensorRect = sensorBounds + } + } + getTouchOverlay()?.apply { touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled overlayTouchListener = TouchExplorationStateChangeListener { if (accessibilityManager.isTouchExplorationEnabled) { @@ -193,7 +225,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } } accessibilityManager.addTouchExplorationStateChangeListener( - overlayTouchListener!! + overlayTouchListener!! ) overlayTouchListener?.onTouchExplorationStateChanged(true) } @@ -211,6 +243,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( view: UdfpsView, controller: UdfpsController ): UdfpsAnimationViewController<*>? { + DeviceEntryUdfpsRefactor.assertInLegacyMode() + val isEnrollment = when (requestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true else -> false @@ -237,39 +271,27 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ) } REASON_AUTH_KEYGUARD -> { - if (DeviceEntryUdfpsRefactor.isEnabled) { - // note: empty controller, currently shows no visual affordance - // instead SysUI will show the fingerprint icon in its DeviceEntryIconView - UdfpsBpViewController( - view.addUdfpsView(R.layout.udfps_bp_view), - statusBarStateController, - primaryBouncerInteractor, - dialogManager, - dumpManager - ) - } else { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityLaunchAnimator, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - selectedUserInteractor, - transitionInteractor, - ) - } + UdfpsKeyguardViewControllerLegacy( + view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { + updateSensorLocation(sensorBounds) + }, + statusBarStateController, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller, + activityLaunchAnimator, + primaryBouncerInteractor, + alternateBouncerInteractor, + udfpsKeyguardAccessibilityDelegate, + selectedUserInteractor, + transitionInteractor, + ) } REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance @@ -302,19 +324,26 @@ class UdfpsControllerOverlay @JvmOverloads constructor( fun hide(): Boolean { val wasShowing = isShowing - overlayView?.apply { + overlayViewLegacy?.apply { if (isDisplayConfigured) { unconfigureDisplay() } + animationViewController = null + } + if (DeviceEntryUdfpsRefactor.isEnabled) { + udfpsDisplayModeProvider.disable(null) + } + getTouchOverlay()?.apply { windowManager.removeView(this) setOnTouchListener(null) setOnHoverListener(null) - animationViewController = null overlayTouchListener?.let { accessibilityManager.removeTouchExplorationStateChangeListener(it) } } - overlayView = null + + overlayViewLegacy = null + overlayTouchView = null overlayTouchListener = null return wasShowing @@ -392,7 +421,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - if (animation !is UdfpsKeyguardViewControllerAdapter) { + val keyguardNotShowing = + if (DeviceEntryUdfpsRefactor.isEnabled) { + !keyguardStateController.isShowing + } else { + animation !is UdfpsKeyguardViewControllerAdapter + } + + if (keyguardNotShowing) { // always rotate view if we're not on the keyguard return true } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt index 039078eb1f21..2484c339a1d4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/CommunalWidgetWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/view/UdfpsTouchOverlay.kt @@ -12,20 +12,15 @@ * WITHOUT 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.communal.ui.view +package com.android.systemui.biometrics.ui.view import android.content.Context import android.util.AttributeSet -import android.widget.LinearLayout -import com.android.systemui.res.R +import android.widget.FrameLayout -/** Wraps around a widget rendered in communal mode. */ -class CommunalWidgetWrapper(context: Context, attrs: AttributeSet? = null) : - LinearLayout(context, attrs) { - init { - id = R.id.communal_widget_wrapper - } -} +/** + * A translucent (not visible to the user) view that receives touches to send to FingerprintManager + * for fingerprint authentication. + */ +class UdfpsTouchOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt new file mode 100644 index 000000000000..5385442092b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -0,0 +1,66 @@ +/* + * 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 com.android.systemui.bouncer.ui.helper + +import androidx.annotation.VisibleForTesting + +/** Enumerates all known adaptive layout configurations. */ +enum class BouncerSceneLayout { + /** The default UI with the bouncer laid out normally. */ + STANDARD, + /** The bouncer is displayed vertically stacked with the user switcher. */ + STACKED, + /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ + SIDE_BY_SIDE, + /** The bouncer is split in two with both sides shown side-by-side. */ + SPLIT, +} + +/** Enumerates the supported window size classes. */ +enum class SizeClass { + COMPACT, + MEDIUM, + EXPANDED, +} + +/** + * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow + * for testing that's not dependent on Compose. + */ +@VisibleForTesting +fun calculateLayoutInternal( + width: SizeClass, + height: SizeClass, + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + return when (height) { + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.MEDIUM -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + SizeClass.EXPANDED -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STACKED + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index ed6a48f93d67..b1c5ab6122fe 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -94,24 +94,23 @@ class PatternBouncerViewModel( * @param yPx The vertical coordinate of the position of the user's pointer, in pixels. * @param containerSizePx The size of the container of the dot grid, in pixels. It's assumed * that the dot grid is perfectly square such that width and height are equal. - * @param verticalOffsetPx How far down from `0` does the dot grid start on the display. */ - fun onDrag(xPx: Float, yPx: Float, containerSizePx: Int, verticalOffsetPx: Float) { + fun onDrag(xPx: Float, yPx: Float, containerSizePx: Int) { val cellWidthPx = containerSizePx / columnCount val cellHeightPx = containerSizePx / rowCount - if (xPx < 0 || yPx < verticalOffsetPx) { + if (xPx < 0 || yPx < 0) { return } val dotColumn = (xPx / cellWidthPx).toInt() - val dotRow = ((yPx - verticalOffsetPx) / cellHeightPx).toInt() + val dotRow = (yPx / cellHeightPx).toInt() if (dotColumn > columnCount - 1 || dotRow > rowCount - 1) { return } val dotPixelX = dotColumn * cellWidthPx + cellWidthPx / 2 - val dotPixelY = dotRow * cellHeightPx + cellHeightPx / 2 + verticalOffsetPx + val dotPixelY = dotRow * cellHeightPx + cellHeightPx / 2 val distance = sqrt((xPx - dotPixelX).pow(2) + (yPx - dotPixelY).pow(2)) val hitRadius = hitFactor * min(cellWidthPx, cellHeightPx) / 2 diff --git a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt index 5e6caf0d0317..27c9b3fa7c9e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/CommonModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt @@ -11,20 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.common -import com.android.systemui.common.domain.interactor.ConfigurationInteractor -import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl +package com.android.systemui.common.data + import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl import dagger.Binds import dagger.Module @Module -abstract class CommonModule { +abstract class CommonDataLayerModule { @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository - - @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt new file mode 100644 index 000000000000..7be2eaf7b105 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt @@ -0,0 +1,27 @@ +/* + * 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 com.android.systemui.common.domain + +import com.android.systemui.common.domain.interactor.ConfigurationInteractor +import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl +import dagger.Binds +import dagger.Module + +@Module +abstract class CommonDomainLayerModule { + @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt new file mode 100644 index 000000000000..8d04e3d338a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt @@ -0,0 +1,28 @@ +/* + * 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 com.android.systemui.common.ui.view + +import android.view.View + +/** + * Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or + * [View#IMPORTANT_FOR_ACCESSIBILITY_NO] based on [value]. + */ +fun View.setImportantForAccessibilityYesNo(value: Boolean) { + importantForAccessibility = + if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt index e50850d9cbbc..a12db6f8f346 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt @@ -91,7 +91,8 @@ constructor( interface CommunalWidgetDao { @Query( "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " + - "ON communal_item_rank_table.uid = communal_widget_table.item_id" + "ON communal_item_rank_table.uid = communal_widget_table.item_id " + + "ORDER BY communal_item_rank_table.rank DESC" ) fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>> @@ -112,6 +113,17 @@ interface CommunalWidgetDao { @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)") fun insertItemRank(rank: Int): Long + @Query("UPDATE communal_item_rank_table SET rank = :order WHERE uid = :itemUid") + fun updateItemRank(itemUid: Long, order: Int) + + @Transaction + fun updateWidgetOrder(ids: List<Int>) { + ids.forEachIndexed { index, it -> + val widget = getWidgetByIdNow(it) + updateItemRank(widget.itemId, ids.size - index) + } + } + @Transaction fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long { return insertWidget( diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index f7fee96c83c2..ded5581a3034 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -61,6 +61,9 @@ interface CommunalWidgetRepository { /** Delete a widget by id from app widget service and the database. */ fun deleteWidget(widgetId: Int) {} + + /** Update the order of widgets in the database. */ + fun updateWidgetOrder(ids: List<Int>) {} } @OptIn(ExperimentalCoroutinesApi::class) @@ -165,6 +168,15 @@ constructor( } } + override fun updateWidgetOrder(ids: List<Int>) { + applicationScope.launch(bgDispatcher) { + communalWidgetDao.updateWidgetOrder(ids) + logger.i({ "Updated the order of widget list with ids: $str1." }) { + str1 = ids.toString() + } + } + } + private fun mapToContentModel( entry: Map.Entry<CommunalItemRank, CommunalWidgetItem> ): CommunalWidgetContentModel { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 7391a5e8e5fc..e630fd4e3c54 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -27,12 +27,12 @@ import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.smartspace.data.repository.SmartspaceRepository import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -47,7 +47,7 @@ constructor( private val widgetRepository: CommunalWidgetRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, - tutorialInteractor: CommunalTutorialInteractor, + keyguardInteractor: KeyguardInteractor, private val appWidgetHost: AppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter ) { @@ -69,6 +69,8 @@ constructor( val isCommunalShowing: Flow<Boolean> = communalRepository.desiredScene.map { it == CommunalSceneKey.Communal } + val isKeyguardVisible: Flow<Boolean> = keyguardInteractor.isKeyguardVisible + /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */ fun onSceneChanged(newScene: CommunalSceneKey) { communalRepository.setDesiredScene(newScene) @@ -86,20 +88,11 @@ constructor( /** Delete a widget by id. */ fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id) - /** A list of all the communal content to be displayed in the communal hub. */ - @OptIn(ExperimentalCoroutinesApi::class) - val communalContent: Flow<List<CommunalContentModel>> = - tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode -> - if (isTutorialMode) { - return@flatMapLatest flowOf(tutorialContent) - } - combine(smartspaceContent, umoContent, widgetContent) { smartspace, umo, widgets -> - smartspace + umo + widgets - } - } + /** Reorder widgets. The order in the list will be their display order in the hub. */ + fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids) /** A list of widget content to be displayed in the communal hub. */ - private val widgetContent: Flow<List<CommunalContentModel.Widget>> = + val widgetContent: Flow<List<CommunalContentModel.Widget>> = widgetRepository.communalWidgets.map { widgets -> widgets.map Widget@{ widget -> return@Widget CommunalContentModel.Widget( @@ -111,7 +104,7 @@ constructor( } /** A flow of available smartspace content. Currently only showing timer targets. */ - private val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> = + val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> = if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) { flowOf(emptyList()) } else { @@ -133,7 +126,7 @@ constructor( } /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */ - private val tutorialContent: List<CommunalContentModel.Tutorial> = + val tutorialContent: List<CommunalContentModel.Tutorial> = listOf( CommunalContentModel.Tutorial(id = 0, CommunalContentSize.FULL), CommunalContentModel.Tutorial(id = 1, CommunalContentSize.THIRD), @@ -145,7 +138,7 @@ constructor( CommunalContentModel.Tutorial(id = 7, CommunalContentSize.HALF), ) - private val umoContent: Flow<List<CommunalContentModel.Umo>> = + val umoContent: Flow<List<CommunalContentModel.Umo>> = mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying -> if (mediaPlaying) { // TODO(b/310254801): support HALF and FULL layouts diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt deleted file mode 100644 index 0daf7b5610e8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt +++ /dev/null @@ -1,80 +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 com.android.systemui.communal.ui.adapter - -import android.appwidget.AppWidgetHost -import android.appwidget.AppWidgetManager -import android.content.Context -import android.util.SizeF -import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo -import com.android.systemui.communal.ui.view.CommunalWidgetWrapper -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.core.Logger -import com.android.systemui.log.dagger.CommunalLog -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -/** Transforms a [CommunalAppWidgetInfo] to a view that renders the widget. */ -class CommunalWidgetViewAdapter -@Inject -constructor( - @Application private val context: Context, - private val appWidgetManager: AppWidgetManager, - private val appWidgetHost: AppWidgetHost, - @CommunalLog logBuffer: LogBuffer, -) { - companion object { - private const val TAG = "CommunalWidgetViewAdapter" - } - - private val logger = Logger(logBuffer, TAG) - - fun adapt(providerInfoFlow: Flow<CommunalAppWidgetInfo?>): Flow<CommunalWidgetWrapper?> = - providerInfoFlow.map { - if (it == null) { - return@map null - } - - val appWidgetId = it.appWidgetId - val providerInfo = it.providerInfo - - if (appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, providerInfo.provider)) { - logger.d("Success binding app widget id: $appWidgetId") - return@map CommunalWidgetWrapper(context).apply { - addView( - appWidgetHost.createView(context, appWidgetId, providerInfo).apply { - // Set the widget to minimum width and height - updateAppWidgetSize( - appWidgetManager.getAppWidgetOptions(appWidgetId), - listOf( - SizeF( - providerInfo.minResizeWidth.toFloat(), - providerInfo.minResizeHeight.toFloat() - ) - ) - ) - } - ) - } - } else { - logger.w("Failed binding app widget id") - return@map null - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt new file mode 100644 index 000000000000..4d8e893cb747 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -0,0 +1,53 @@ +/* + * 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 com.android.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.media.controls.ui.MediaHost +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +/** The base view model for the communal hub. */ +abstract class BaseCommunalViewModel( + private val communalInteractor: CommunalInteractor, + val mediaHost: MediaHost, +) { + val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible + + val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene + + fun onSceneChanged(scene: CommunalSceneKey) { + communalInteractor.onSceneChanged(scene) + } + + /** A list of all the communal content to be displayed in the communal hub. */ + abstract val communalContent: Flow<List<CommunalContentModel>> + + /** Whether in edit mode for the communal hub. */ + open val isEditMode = false + + /** Called as the UI requests deleting a widget. */ + open fun onDeleteWidget(id: Int) {} + + /** Called as the UI requests reordering widgets. */ + open fun onReorderWidgets(ids: List<Int>) {} + + /** Called as the UI requests opening the widget editor. */ + open fun onOpenWidgetEditor() {} +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt new file mode 100644 index 000000000000..111f8b4ca48f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -0,0 +1,46 @@ +/* + * 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 com.android.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.media.dagger.MediaModule +import javax.inject.Inject +import javax.inject.Named +import kotlinx.coroutines.flow.Flow + +/** The view model for communal hub in edit mode. */ +@SysUISingleton +class CommunalEditModeViewModel +@Inject +constructor( + private val communalInteractor: CommunalInteractor, + @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, +) : BaseCommunalViewModel(communalInteractor, mediaHost) { + + override val isEditMode = true + + // Only widgets are editable. + override val communalContent: Flow<List<CommunalContentModel>> = + communalInteractor.widgetContent + + override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id) + + override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 14edc8e0a88c..11bde6bd7af0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -17,34 +17,42 @@ package com.android.systemui.communal.ui.viewmodel import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.dagger.MediaModule import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +/** The default view model used for showing the communal hub. */ @SysUISingleton class CommunalViewModel @Inject constructor( private val communalInteractor: CommunalInteractor, - @Named(MediaModule.COMMUNAL_HUB) val mediaHost: MediaHost, -) { - val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene - fun onSceneChanged(scene: CommunalSceneKey) { - communalInteractor.onSceneChanged(scene) - } + tutorialInteractor: CommunalTutorialInteractor, + @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, +) : BaseCommunalViewModel(communalInteractor, mediaHost) { + @OptIn(ExperimentalCoroutinesApi::class) + override val communalContent: Flow<List<CommunalContentModel>> = + tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode -> + if (isTutorialMode) { + return@flatMapLatest flowOf(communalInteractor.tutorialContent) + } + combine( + communalInteractor.smartspaceContent, + communalInteractor.umoContent, + communalInteractor.widgetContent, + ) { smartspace, umo, widgets -> + smartspace + umo + widgets + } + } - /** A list of all the communal content to be displayed in the communal hub. */ - val communalContent: Flow<List<CommunalContentModel>> = communalInteractor.communalContent - - /** Delete a widget by id. */ - fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id) - - /** Open the widget editor */ - fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() + override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 78e85db9ea05..7b94fc182fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -20,17 +20,21 @@ import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.os.Bundle import android.util.Log -import android.view.View import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.res.R +import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent import javax.inject.Inject /** An Activity for editing the widgets that appear in hub mode. */ -class EditWidgetsActivity @Inject constructor(private val communalInteractor: CommunalInteractor) : - ComponentActivity() { +class EditWidgetsActivity +@Inject +constructor( + private val communalViewModel: CommunalEditModeViewModel, + private val communalInteractor: CommunalInteractor, +) : ComponentActivity() { companion object { /** * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode. @@ -59,20 +63,19 @@ class EditWidgetsActivity @Inject constructor(private val communalInteractor: Co "Failed to receive result from widget picker, code=${result.resultCode}" ) } - this@EditWidgetsActivity.finish() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setShowWhenLocked(true) - setContentView(R.layout.edit_widgets) - - val addWidgetsButton = findViewById<View>(R.id.add_widget) - addWidgetsButton?.setOnClickListener({ - addWidgetActivityLauncher.launch( - Intent(applicationContext, WidgetPickerActivity::class.java) - ) - }) + setCommunalEditWidgetActivityContent( + activity = this, + viewModel = communalViewModel, + onOpenWidgetPicker = { + addWidgetActivityLauncher.launch( + Intent(applicationContext, WidgetPickerActivity::class.java) + ) + }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt index 3e6dbd5a7115..a2765486bf2d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt @@ -43,7 +43,6 @@ constructor( super.onCreate(savedInstanceState) setContentView(R.layout.widget_picker) - setShowWhenLocked(true) loadWidgets() } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 4bdea75d9d71..65d44957222a 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -22,7 +22,7 @@ import android.view.View import android.view.WindowInsets import androidx.activity.ComponentActivity import androidx.lifecycle.LifecycleOwner -import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene @@ -58,6 +58,13 @@ interface BaseComposeFacade { onResult: (PeopleViewModel.Result) -> Unit, ) + /** Bind the content of [activity] to [viewModel]. */ + fun setCommunalEditWidgetActivityContent( + activity: ComponentActivity, + viewModel: BaseCommunalViewModel, + onOpenWidgetPicker: () -> Unit, + ) + /** Create a [View] to represent [viewModel] on screen. */ fun createFooterActionsView( context: Context, @@ -77,9 +84,9 @@ interface BaseComposeFacade { /** Create a [View] to represent [viewModel] on screen. */ fun createCommunalView( context: Context, - viewModel: CommunalViewModel, + viewModel: BaseCommunalViewModel, ): View /** Creates a container that hosts the communal UI and handles gesture transitions. */ - fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View + fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 5f54a98fe05d..0405ca43320f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -42,7 +42,8 @@ import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule; import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; -import com.android.systemui.common.CommonModule; +import com.android.systemui.common.data.CommonDataLayerModule; +import com.android.systemui.common.domain.CommonDomainLayerModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; @@ -106,8 +107,6 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; @@ -178,7 +177,8 @@ import javax.inject.Named; ClipboardOverlayModule.class, ClockRegistryModule.class, CommunalModule.class, - CommonModule.class, + CommonDataLayerModule.class, + CommonDomainLayerModule.class, ConnectivityModule.class, ControlsModule.class, CoroutinesModule.class, @@ -374,11 +374,4 @@ public abstract class SystemUIModule { @Binds abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator( LargeScreenShadeInterpolatorImpl impl); - - @SysUISingleton - @Provides - static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider( - NotificationInterruptStateProvider innerProvider) { - return new NotificationInterruptStateProviderWrapper(innerProvider); - } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7d541070f146..98fda3eb01c5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -464,12 +464,6 @@ object Flags { val WALLPAPER_MULTI_CROP = sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false) - // TODO(b/290220798): Tracking Bug - @Keep - @JvmField - val ENABLE_PIP2_IMPLEMENTATION = - sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false) - // 1200 - predictive back @Keep @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt new file mode 100644 index 000000000000..bc1cc4f4bc56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt @@ -0,0 +1,65 @@ +/* + * 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 com.android.systemui.fold.ui.helper + +import androidx.annotation.VisibleForTesting +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo + +sealed interface FoldPosture { + /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ + data object Folded : FoldPosture + /** A foldable that's halfway open with the hinge held vertically. */ + data object Book : FoldPosture + /** A foldable that's halfway open with the hinge held horizontally. */ + data object Tabletop : FoldPosture + /** A foldable that's fully unfolded / flat. */ + data object FullyUnfolded : FoldPosture +} + +/** + * Internal version of `foldPosture` in the System UI Compose library, extracted here to allow for + * testing that's not dependent on Compose. + */ +@VisibleForTesting +fun foldPostureInternal(layoutInfo: WindowLayoutInfo?): FoldPosture { + return layoutInfo + ?.displayFeatures + ?.firstNotNullOfOrNull { it as? FoldingFeature } + .let { foldingFeature -> + when (foldingFeature?.state) { + null -> FoldPosture.Folded + FoldingFeature.State.HALF_OPENED -> foldingFeature.orientation.toHalfwayPosture() + FoldingFeature.State.FLAT -> + if (foldingFeature.isSeparating) { + // Dual screen device. + foldingFeature.orientation.toHalfwayPosture() + } else { + FoldPosture.FullyUnfolded + } + else -> error("Unsupported state \"${foldingFeature.state}\"") + } + } +} + +private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { + return when (this) { + FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop + FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book + else -> error("Unsupported orientation \"$this\"") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 4e6a872cb3f7..fe9865b2d1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2720,9 +2720,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void updateActivityLockScreenState(boolean showing, boolean aodShowing) { mUiBgExecutor.execute(() -> { - if (DEBUG) { - Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")"); - } + Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")"); if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { // Handled in WmLockscreenVisibilityManager if flag is enabled. @@ -3251,10 +3249,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, DejankUtils.postAfterTraversal(() -> { if (!mPM.isInteractive() && !mPendingLock) { Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal:" - + "mPM.isInteractive()=" + mPM.isInteractive() - + "mPendingLock=" + mPendingLock + "." - + "One of these being false means we re-locked the device during unlock. " - + "Do not proceed to finish keyguard exit and unlock."); + + " mPM.isInteractive()=" + mPM.isInteractive() + + " mPendingLock=" + mPendingLock + "." + + " One of these being false means we re-locked the device during unlock." + + " Do not proceed to finish keyguard exit and unlock."); doKeyguardLocked(null); finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); // Ensure WM is notified that we made a decision to show diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index c5a8375f5576..eee5206498e4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -17,13 +17,11 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint -import android.content.Intent import android.graphics.Rect import android.graphics.drawable.Animatable2 import android.util.Size import android.view.View import android.view.ViewGroup -import android.view.ViewPropertyAnimator import android.widget.ImageView import androidx.core.animation.CycleInterpolator import androidx.core.animation.ObjectAnimator @@ -34,7 +32,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.settingslib.Utils -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableLinearLayout @@ -43,9 +40,12 @@ import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils +import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -79,7 +79,7 @@ object KeyguardBottomAreaViewBinder { * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after * it is bound. */ - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] @Deprecated("Deprecated as part of b/278057014") interface Binding { /** Notifies that device configuration has changed. */ @@ -133,8 +133,7 @@ object KeyguardBottomAreaViewBinder { val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] launch { viewModel.startButton.collect { buttonModel -> updateButton( @@ -147,7 +146,7 @@ object KeyguardBottomAreaViewBinder { } } - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] launch { viewModel.endButton.collect { buttonModel -> updateButton( @@ -185,7 +184,7 @@ object KeyguardBottomAreaViewBinder { } } - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] launch { updateButtonAlpha( view = startButton, @@ -194,7 +193,7 @@ object KeyguardBottomAreaViewBinder { ) } - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] launch { updateButtonAlpha( view = endButton, @@ -220,7 +219,7 @@ object KeyguardBottomAreaViewBinder { } } - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] launch { configurationBasedDimensions.collect { dimensions -> startButton.updateLayoutParams<ViewGroup.LayoutParams> { @@ -378,13 +377,14 @@ object KeyguardBottomAreaViewBinder { view.isClickable = viewModel.isClickable if (viewModel.isClickable) { if (viewModel.useLongPress) { - val onTouchListener = KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) + val onTouchListener = + KeyguardQuickAffordanceOnTouchListener( + view, + viewModel, + messageDisplayer, + vibratorHelper, + falsingManager, + ) view.setOnTouchListener(onTouchListener) view.setOnClickListener { messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) @@ -403,9 +403,7 @@ object KeyguardBottomAreaViewBinder { KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds shakeAnimator.interpolator = CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) - shakeAnimator.doOnEnd { - view.translationX = 0f - } + shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) @@ -425,7 +423,7 @@ object KeyguardBottomAreaViewBinder { } @Deprecated("Deprecated as part of b/278057014") - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] private suspend fun updateButtonAlpha( view: View, viewModel: Flow<KeyguardQuickAffordanceViewModel>, @@ -456,7 +454,7 @@ object KeyguardBottomAreaViewBinder { } @Deprecated("Deprecated as part of b/278057014") - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] private class OnLongClickListener( private val falsingManager: FalsingManager?, private val viewModel: KeyguardQuickAffordanceViewModel, @@ -493,7 +491,7 @@ object KeyguardBottomAreaViewBinder { } @Deprecated("Deprecated as part of b/278057014") - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] private class OnClickListener( private val viewModel: KeyguardQuickAffordanceViewModel, private val falsingManager: FalsingManager, @@ -535,13 +533,7 @@ object KeyguardBottomAreaViewBinder { view: View, ) { activityStarter.postStartActivityDismissingKeyguard( - Intent(Intent.ACTION_SET_WALLPAPER).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - view.context - .getString(R.string.config_wallpaperPickerPackage) - .takeIf { it.isNotEmpty() } - ?.let { packageName -> setPackage(packageName) } - }, + WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD), /* delay= */ 0, /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view), /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) @@ -549,7 +541,7 @@ object KeyguardBottomAreaViewBinder { } @Deprecated("Deprecated as part of b/278057014") - //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] + // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] private data class ConfigurationBasedDimensions( val defaultBurnInPreventionYOffsetPx: Int, val buttonSizePx: Size, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt index 6beef8ec1ff3..8514225fda90 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt @@ -17,19 +17,20 @@ package com.android.systemui.keyguard.ui.binder -import android.content.Intent import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.view.LaunchableLinearLayout import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel +import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils +import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.distinctUntilChanged @@ -98,13 +99,7 @@ object KeyguardSettingsViewBinder { view: View, ) { activityStarter.postStartActivityDismissingKeyguard( - Intent(Intent.ACTION_SET_WALLPAPER).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - view.context - .getString(R.string.config_wallpaperPickerPackage) - .takeIf { it.isNotEmpty() } - ?.let { packageName -> setPackage(packageName) } - }, + WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD), /* delay= */ 0, /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view), /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) @@ -127,5 +122,4 @@ object KeyguardSettingsViewBinder { } .start() } - -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt b/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt new file mode 100644 index 000000000000..84e05661d05a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/util/WallpaperPickerIntentUtils.kt @@ -0,0 +1,39 @@ +/* + * 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 com.android.systemui.keyguard.util + +import android.content.Context +import android.content.Intent +import com.android.systemui.res.R + +/** Provides function(s) to get intent for launching the Wallpaper Picker app. */ +object WallpaperPickerIntentUtils { + + fun getIntent(context: Context, launchSource: String): Intent { + return Intent(Intent.ACTION_SET_WALLPAPER).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + context + .getString(R.string.config_wallpaperPickerPackage) + .takeIf { it.isNotEmpty() } + ?.let { packageName -> setPackage(packageName) } + putExtra(WALLPAPER_LAUNCH_SOURCE, launchSource) + } + } + + private const val WALLPAPER_LAUNCH_SOURCE = "com.android.wallpaper.LAUNCH_SOURCE" + const val LAUNCH_SOURCE_KEYGUARD = "app_launched_keyguard" +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index f2db088ced83..44232ffb3ad7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -19,12 +19,18 @@ package com.android.systemui.media.controls.util import android.app.StatusBarManager import android.os.UserHandle import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.scene.shared.flag.SceneContainerFlags import javax.inject.Inject @SysUISingleton -class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { +class MediaFlags +@Inject +constructor( + private val featureFlags: FeatureFlagsClassic, + private val sceneContainerFlags: SceneContainerFlags +) { /** * Check whether media control actions should be based on PlaybackState instead of notification */ @@ -48,4 +54,8 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { /** Check whether we allow remote media to generate resume controls */ fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME) + + /** Check whether to use flexiglass layout */ + fun isFlexiglassEnabled() = + sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt new file mode 100644 index 000000000000..898298c58b32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt @@ -0,0 +1,48 @@ +/* + * 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 com.android.systemui.media.controls.util + +import com.android.systemui.Flags +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the media_in_scene_container flag state. */ +@Suppress("NOTHING_TO_INLINE") +object MediaInSceneContainerFlag { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER + + /** Is the flag enabled? */ + @JvmStatic + inline val isEnabled + get() = Flags.mediaInSceneContainer() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 9f5e1b79765e..0320dec0580f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -278,6 +278,7 @@ public class NavigationBarControllerImpl implements @Override public void onDisplayRemoved(int displayId) { removeNavigationBar(displayId); + mHasNavBar.delete(displayId); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index fa18b35b215e..052c0daf0b56 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -12,6 +12,7 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,7 @@ import android.view.animation.OvershootInterpolator; import android.widget.Scroller; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -43,6 +45,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private static final int NO_PAGE = -1; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; + private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300; private static final float BOUNCE_ANIMATION_TENSION = 1.3f; private static final long BOUNCE_ANIMATION_DURATION = 450L; private static final int TILE_ANIMATION_STAGGER_DELAY = 85; @@ -63,8 +66,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private PageListener mPageListener; private boolean mListening; - private Scroller mScroller; + @VisibleForTesting Scroller mScroller; + /* set of animations used to indicate which tiles were just revealed */ @Nullable private AnimatorSet mBounceAnimatorSet; private float mLastExpansion; @@ -306,6 +310,38 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); mPageIndicator.setLocation(mPageIndicatorPosition); + mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need + // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we + // have a chance to intercept ACTION_UP. + if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) { + scrollByX(getDeltaXForKeyboardScrolling(keyCode), + SINGLE_PAGE_SCROLL_DURATION_MILLIS); + } + return true; + } + return false; + }); + } + + private int getDeltaXForKeyboardScrolling(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) { + return -getWidth(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + && getCurrentItem() != mPages.size() - 1) { + return getWidth(); + } + return 0; + } + + private void scrollByX(int x, int durationMillis) { + if (x != 0) { + mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(), + /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis); + // scroller just sets its state, we need to invalidate view to actually start scrolling + postInvalidateOnAnimation(); + } } @Override @@ -596,9 +632,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }); setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated. int dx = getWidth() * lastPageNumber; - mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0, - REVEAL_SCROLL_DURATION_MILLIS); - postInvalidateOnAnimation(); + scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS); } private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt new file mode 100644 index 000000000000..b2b226464ee5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -0,0 +1,60 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight.domain + +import android.content.Context +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [FlashlightTileModel] to [QSTileState]. */ +class FlashlightMapper @Inject constructor(private val context: Context) : + QSTileDataToStateMapper<FlashlightTileModel> { + + override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = + QSTileState.build(context, config.uiConfig) { + val icon = + Icon.Loaded( + context.resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_flashlight_icon_on + } else { + R.drawable.qs_flashlight_icon_off + } + ), + contentDescription = null + ) + this.icon = { icon } + + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = context.resources.getStringArray(R.array.tile_states_flashlight)[1] + } + contentDescription = label + supportedActions = + setOf( + QSTileState.UserAction.CLICK, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt new file mode 100644 index 000000000000..53d4cf96809c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt @@ -0,0 +1,62 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight.domain.interactor + +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.statusbar.policy.FlashlightController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Observes flashlight state changes providing the [FlashlightTileModel]. */ +class FlashlightTileDataInteractor +@Inject +constructor( + private val flashlightController: FlashlightController, +) : QSTileDataInteractor<FlashlightTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<FlashlightTileModel> = conflatedCallbackFlow { + val initialValue = flashlightController.isEnabled + trySend(FlashlightTileModel(initialValue)) + + val callback = + object : FlashlightController.FlashlightListener { + override fun onFlashlightChanged(enabled: Boolean) { + trySend(FlashlightTileModel(enabled)) + } + override fun onFlashlightError() { + trySend(FlashlightTileModel(false)) + } + override fun onFlashlightAvailabilityChanged(available: Boolean) { + trySend(FlashlightTileModel(flashlightController.isEnabled)) + } + } + flashlightController.addCallback(callback) + awaitClose { flashlightController.removeCallback(callback) } + } + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(flashlightController.hasFlashlight()) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt new file mode 100644 index 000000000000..9180e3080d07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt @@ -0,0 +1,45 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight.domain.interactor + +import android.app.ActivityManager +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.FlashlightController +import javax.inject.Inject + +/** Handles flashlight tile clicks. */ +class FlashlightTileUserActionInteractor +@Inject +constructor( + private val flashlightController: FlashlightController, +) : QSTileUserActionInteractor<FlashlightTileModel> { + + override suspend fun handleInput(input: QSTileInput<FlashlightTileModel>) = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + if (!ActivityManager.isUserAMonkey()) { + flashlightController.setFlashlight(!input.data.isEnabled) + } + } + else -> {} + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt new file mode 100644 index 000000000000..ef6b2be06747 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt @@ -0,0 +1,24 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight.domain.model + +/** + * Flashlight tile model. + * + * @param isEnabled is true when the falshlight is enabled; + */ +@JvmInline value class FlashlightTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 346d5c30a63e..e5e1e8445e94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -33,14 +32,10 @@ import kotlinx.coroutines.flow.map class QuickSettingsSceneViewModel @Inject constructor( - private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, val qsSceneAdapter: QSSceneAdapter, val notifications: NotificationsPlaceholderViewModel, ) { - /** Notifies that some content in quick settings was clicked. */ - fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() - val destinationScenes = qsSceneAdapter.isCustomizing.map { customizing -> if (customizing) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index b30bc56aeeb0..bc5090f14d23 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -30,12 +30,11 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; +import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.haptics.slider.SeekableSliderEventProducer; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -282,7 +281,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final VibratorHelper mVibratorHelper; private final SystemClock mSystemClock; private final CoroutineDispatcher mMainDispatcher; - private final ActivityStarter mActivityStarter; @Inject public Factory( @@ -290,14 +288,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV UiEventLogger uiEventLogger, VibratorHelper vibratorHelper, SystemClock clock, - @Main CoroutineDispatcher mainDispatcher, - ActivityStarter activityStarter) { + @Main CoroutineDispatcher mainDispatcher + ) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mVibratorHelper = vibratorHelper; mSystemClock = clock; mMainDispatcher = mainDispatcher; - mActivityStarter = activityStarter; } /** @@ -313,8 +310,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); - root.setActivityStarter(mActivityStarter); - BrightnessSliderHapticPlugin plugin; if (hapticBrightnessSlider()) { plugin = new BrightnessSliderHapticPluginImpl( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index 5ecf07f5a264..c88549224183 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -33,7 +33,6 @@ import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; /** @@ -42,7 +41,6 @@ import com.android.systemui.res.R; */ public class BrightnessSliderView extends FrameLayout { - private ActivityStarter mActivityStarter; @NonNull private ToggleSeekBar mSlider; private DispatchTouchEventListener mListener; @@ -59,10 +57,6 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } - // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { @@ -71,7 +65,6 @@ public class BrightnessSliderView extends FrameLayout { mSlider = requireViewById(R.id.slider); mSlider.setAccessibilityLabel(getContentDescription().toString()); - mSlider.setActivityStarter(mActivityStarter); // Finds the progress drawable. Assumes brightness_progress_drawable.xml try { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 6ec10da28000..a5a0ae70045e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -23,9 +23,8 @@ import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; -import androidx.annotation.NonNull; - import com.android.settingslib.RestrictedLockUtils; +import com.android.systemui.Dependency; import com.android.systemui.plugins.ActivityStarter; public class ToggleSeekBar extends SeekBar { @@ -33,8 +32,6 @@ public class ToggleSeekBar extends SeekBar { private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; - private ActivityStarter mActivityStarter; - public ToggleSeekBar(Context context) { super(context); } @@ -52,7 +49,7 @@ public class ToggleSeekBar extends SeekBar { if (mEnforcedAdmin != null) { Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( mContext, mEnforcedAdmin); - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); return true; } if (!isEnabled()) { @@ -77,8 +74,4 @@ public class ToggleSeekBar extends SeekBar { public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { mEnforcedAdmin = admin; } - - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index a44b4b4f11fe..c810786681f0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -184,6 +184,8 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -604,6 +606,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + private final ActiveNotificationsInteractor mActiveNotificationsInteractor; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; @@ -774,6 +777,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardInteractor keyguardInteractor, ActivityStarter activityStarter, SharedNotificationContainerInteractor sharedNotificationContainerInteractor, + ActiveNotificationsInteractor activeNotificationsInteractor, KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, SplitShadeStateController splitShadeStateController, @@ -804,6 +808,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor; + mActiveNotificationsInteractor = activeNotificationsInteractor; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; @@ -1795,9 +1800,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean hasVisibleNotifications() { - return mNotificationStackScrollLayoutController - .getVisibleNotificationCount() != 0 - || mMediaDataManager.hasActiveMediaOrRecommendation(); + if (FooterViewRefactor.isEnabled()) { + return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + || mMediaDataManager.hasActiveMediaOrRecommendation(); + } else { + return mNotificationStackScrollLayoutController + .getVisibleNotificationCount() != 0 + || mMediaDataManager.hasActiveMediaOrRecommendation(); + } } /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ @@ -3004,7 +3014,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; - mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); + } updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 868fbceecafc..e84bfc512eb5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -87,6 +87,8 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -106,12 +108,12 @@ import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; +import dagger.Lazy; + import java.io.PrintWriter; import javax.inject.Inject; -import dagger.Lazy; - /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ @@ -157,6 +159,7 @@ public class QuickSettingsController implements Dumpable { private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; private final ShadeInteractor mShadeInteractor; + private final ActiveNotificationsInteractor mActiveNotificationsInteractor; private final JavaAdapter mJavaAdapter; private final FalsingManager mFalsingManager; private final AccessibilityManager mAccessibilityManager; @@ -339,6 +342,7 @@ public class QuickSettingsController implements Dumpable { KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, ShadeRepository shadeRepository, ShadeInteractor shadeInteractor, + ActiveNotificationsInteractor activeNotificationsInteractor, JavaAdapter javaAdapter, CastController castController, SplitShadeStateController splitShadeStateController @@ -386,6 +390,7 @@ public class QuickSettingsController implements Dumpable { mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; + mActiveNotificationsInteractor = activeNotificationsInteractor; mJavaAdapter = javaAdapter; mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); @@ -983,7 +988,9 @@ public class QuickSettingsController implements Dumpable { void updateQsState() { boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled; mShadeRepository.setLegacyQsFullscreen(qsFullScreen); - mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); + if (!FooterViewRefactor.isEnabled()) { + mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); + } mNotificationStackScrollLayoutController.setScrollingEnabled( mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll)); @@ -2230,8 +2237,12 @@ public class QuickSettingsController implements Dumpable { mLockscreenShadeTransitionController.getQSDragProgress()); setExpansionHeight(qsHeight); } - if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 - && !mMediaDataManager.hasActiveMediaOrRecommendation()) { + + boolean hasNotifications = FooterViewRefactor.isEnabled() + ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + : mNotificationStackScrollLayoutController.getVisibleNotificationCount() + != 0; + if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) { // No notifications are visible, let's animate to the height of qs instead if (isQsFragmentCreated()) { // Let's interpolate to the header height instead of the top padding, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index de334bbd880c..2338be28d32c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -85,7 +85,9 @@ public class EmptyShadeView extends StackScrollerDecorView { public void setFooterVisibility(@Visibility int visibility) { mFooterVisibility = visibility; - setSecondaryVisible(visibility == View.VISIBLE, false); + setSecondaryVisible(/* visible = */ visibility == View.VISIBLE, + /* animate = */false, + /* onAnimationEnded = */ null); } public void setFooterText(@StringRes int text) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index ae765e40c790..49c729eada1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -160,8 +160,15 @@ constructor( var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null /** The touch helper responsible for the drag down animation. */ - val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, - naturalScrollingSettingObserver, context) + val touchHelper = + DragDownHelper( + falsingManager, + falsingCollector, + this, + naturalScrollingSettingObserver, + shadeRepository, + context + ) private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy { splitShadeOverScrollerFactory.create({ qS }, { nsslController }) @@ -756,6 +763,7 @@ class DragDownHelper( private val falsingCollector: FalsingCollector, private val dragDownCallback: LockscreenShadeTransitionController, private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver, + private val shadeRepository: ShadeRepository, context: Context ) : Gefingerpoken { @@ -808,8 +816,9 @@ class DragDownHelper( startingChild = null initialTouchY = y initialTouchX = x - isTrackpadReverseScroll = !naturalScrollingSettingObserver.isNaturalScrollingEnabled - && isTrackpadScroll(true, event) + isTrackpadReverseScroll = + !naturalScrollingSettingObserver.isNaturalScrollingEnabled && + isTrackpadScroll(true, event) } MotionEvent.ACTION_MOVE -> { val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY) @@ -875,6 +884,7 @@ class DragDownHelper( } isDraggingDown = false isTrackpadReverseScroll = false + shadeRepository.setLegacyLockscreenShadeTracking(false) } else { stopDragging() return false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 64970e456c1d..fa2748c1dc77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -16,17 +16,19 @@ package com.android.systemui.statusbar.notification.collection.coordinator +import com.android.app.tracing.traceSection import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.app.tracing.traceSection import javax.inject.Inject /** @@ -40,6 +42,7 @@ internal constructor( private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val notificationIconAreaController: NotificationIconAreaController, private val renderListInteractor: RenderNotificationListInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -49,8 +52,14 @@ internal constructor( fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { - controller.setNotifStats(calculateNotifStats(entries)) - if (NotificationIconContainerRefactor.isEnabled) { + val notifStats = calculateNotifStats(entries) + if (FooterViewRefactor.isEnabled) { + activeNotificationsInteractor.setNotifStats(notifStats) + } + // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer + // visibility is handled in the new stack. + controller.setNotifStats(notifStats) + if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) { renderListInteractor.setRenderedList(entries) } else { notificationIconAreaController.updateNotificationIcons(entries) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt index fde4ecb7bcaa..a37937a6c495 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt @@ -26,6 +26,7 @@ interface NotifStackController { /** Data provided to the NotificationRootController whenever the pipeline runs */ data class NotifStats( + // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag. val numActiveNotifs: Int, val hasNonClearableAlertingNotifs: Boolean, val hasClearableAlertingNotifs: Boolean, @@ -33,17 +34,16 @@ data class NotifStats( val hasClearableSilentNotifs: Boolean ) { companion object { - @JvmStatic - val empty = NotifStats(0, false, false, false, false) + @JvmStatic val empty = NotifStats(0, false, false, false, false) } } /** * An implementation of NotifStackController which provides default, no-op implementations of each - * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding - * methods, rather than forcing us to add no-op implementations in their implementation every time - * a method is added. + * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods, + * rather than forcing us to add no-op implementations in their implementation every time a method + * is added. */ open class DefaultNotifStackController @Inject constructor() : NotifStackController { override fun setNotifStats(stats: NotifStats) {} -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 860ad630be0c..0f14135f1d4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -64,6 +64,10 @@ import com.android.systemui.statusbar.notification.init.NotificationsControllerS import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProviderModule; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderImpl; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl; @@ -268,4 +272,24 @@ public interface NotificationsModule { /** */ @Binds NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl); + + /** */ + @Provides + @SysUISingleton + static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider( + Provider<NotificationInterruptStateProviderImpl> oldImplProvider, + Provider<VisualInterruptionDecisionProviderImpl> newImplProvider) { + if (VisualInterruptionRefactor.isEnabled()) { + return newImplProvider.get(); + } else { + return new NotificationInterruptStateProviderWrapper(oldImplProvider.get()); + } + } + + /** */ + @Binds + @IntoMap + @ClassKey(VisualInterruptionDecisionProvider.class) + CoreStartable startVisualInterruptionDecisionProvider( + VisualInterruptionDecisionProvider provider); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt index 12ee54d4977d..5ed82cc1ed5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel @@ -37,6 +38,9 @@ class ActiveNotificationListRepository @Inject constructor() { /** Are any already-seen notifications currently filtered out of the active list? */ val hasFilteredOutSeenNotifications = MutableStateFlow(false) + + /** Stats about the list of notifications attached to the shade */ + val notifStats = MutableStateFlow(NotifStats.empty) } /** Represents the notification list, comprised of groups and individual notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 85ba205d3a0a..31893b402e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -15,17 +15,19 @@ package com.android.systemui.statusbar.notification.domain.interactor +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map class ActiveNotificationsInteractor @Inject constructor( - repository: ActiveNotificationListRepository, + private val repository: ActiveNotificationListRepository, ) { /** Notifications actively presented to the user in the notification stack, in order. */ val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> = @@ -40,4 +42,25 @@ constructor( } } } + + /** Are any notifications being actively presented in the notification stack? */ + val areAnyNotificationsPresent: Flow<Boolean> = + repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged() + + /** + * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous + * code. + */ + val areAnyNotificationsPresentValue: Boolean + get() = repository.activeNotifications.value.renderList.isNotEmpty() + + /** Are there are any notifications that can be cleared by the "Clear all" button? */ + val hasClearableNotifications: Flow<Boolean> = + repository.notifStats + .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs } + .distinctUntilChanged() + + fun setNotifStats(notifStats: NotifStats) { + repository.notifStats.value = notifStats + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 10a43d53353d..3184d5efe5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.util.DumpUtilsKt; import java.io.PrintWriter; +import java.util.function.Consumer; public class FooterView extends StackScrollerDecorView { private static final String TAG = "FooterView"; @@ -63,9 +64,13 @@ public class FooterView extends StackScrollerDecorView { private String mSeenNotifsFilteredText; private Drawable mSeenNotifsFilteredIcon; + private @StringRes int mClearAllButtonTextId; + private @StringRes int mClearAllButtonDescriptionId; private @StringRes int mMessageStringId; private @DrawableRes int mMessageIconId; + private OnClickListener mClearAllButtonClickListener; + public FooterView(Context context, AttributeSet attrs) { super(context, attrs); } @@ -84,12 +89,18 @@ public class FooterView extends StackScrollerDecorView { return isSecondaryVisible(); } + /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */ + public void setClearAllButtonVisible(boolean visible, boolean animate) { + setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null); + } + /** * Set the visibility of the "Clear all" button to {@code visible}. Animate the change if * {@code animate} is true. */ - public void setClearAllButtonVisible(boolean visible, boolean animate) { - setSecondaryVisible(visible, animate); + public void setClearAllButtonVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { + setSecondaryVisible(visible, animate, onAnimationEnded); } @Override @@ -106,6 +117,42 @@ public class FooterView extends StackScrollerDecorView { }); } + /** Set the text label for the "Clear all" button. */ + public void setClearAllButtonText(@StringRes int textId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; + if (mClearAllButtonTextId == textId) { + return; // nothing changed + } + mClearAllButtonTextId = textId; + updateClearAllButtonText(); + } + + private void updateClearAllButtonText() { + if (mClearAllButtonTextId == 0) { + return; // not initialized yet + } + mClearAllButton.setText(getContext().getString(mClearAllButtonTextId)); + } + + /** Set the accessibility content description for the "Clear all" button. */ + public void setClearAllButtonDescription(@StringRes int contentDescriptionId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + return; + } + if (mClearAllButtonDescriptionId == contentDescriptionId) { + return; // nothing changed + } + mClearAllButtonDescriptionId = contentDescriptionId; + updateClearAllButtonDescription(); + } + + private void updateClearAllButtonDescription() { + if (mClearAllButtonDescriptionId == 0) { + return; // not initialized yet + } + mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId)); + } + /** Set the string for a message to be shown instead of the buttons. */ public void setMessageString(@StringRes int messageId) { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; @@ -181,6 +228,10 @@ public class FooterView extends StackScrollerDecorView { /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { + if (FooterViewRefactor.isEnabled()) { + if (mClearAllButtonClickListener == listener) return; + mClearAllButtonClickListener = listener; + } mClearAllButton.setOnClickListener(listener); } @@ -214,7 +265,28 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setText(mManageNotificationText); mManageButton.setContentDescription(mManageNotificationText); } - if (!FooterViewRefactor.isEnabled()) { + if (FooterViewRefactor.isEnabled()) { + updateClearAllButtonText(); + updateClearAllButtonDescription(); + + updateMessageString(); + updateMessageIcon(); + } else { + // NOTE: Prior to the refactor, `updateResources` set the class properties to the right + // string values. It was always being called together with `updateContent`, which + // deals with actually associating those string values with the correct views + // (buttons or text). + // In the new code, the resource IDs are being set in the view binder (through + // setMessageString and similar setters). The setters themselves now deal with + // updating both the resource IDs and the views where appropriate (as in, calling + // `updateMessageString` when the resource ID changes). This eliminates the need for + // `updateResources`, which will eventually be removed. There are, however, still + // situations in which we want to update the views even if the resource IDs didn't + // change, such as configuration changes. + mClearAllButton.setText(R.string.clear_all_notifications_text); + mClearAllButton.setContentDescription( + mContext.getString(R.string.accessibility_clear_all)); + mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); mSeenNotifsFooterTextView .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); @@ -230,16 +302,8 @@ public class FooterView extends StackScrollerDecorView { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateColors(); - mClearAllButton.setText(R.string.clear_all_notifications_text); - mClearAllButton.setContentDescription( - mContext.getString(R.string.accessibility_clear_all)); updateResources(); updateContent(); - - if (FooterViewRefactor.isEnabled()) { - updateMessageString(); - updateMessageIcon(); - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt index 6d8234371b65..0299114e0afc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder +import android.view.View import androidx.lifecycle.lifecycleScope import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.stopAnimating +import com.android.systemui.util.ui.value import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch @@ -28,9 +32,31 @@ object FooterViewBinder { fun bind( footer: FooterView, viewModel: FooterViewModel, + clearAllNotifications: View.OnClickListener, ): DisposableHandle { + // Listen for changes when the view is attached. return footer.repeatWhenAttached { - // Listen for changes when the view is attached. + lifecycleScope.launch { + viewModel.clearAllButton.collect { button -> + if (button.isVisible.isAnimating) { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ true, + ) { _ -> + button.isVisible.stopAnimating() + } + } else { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ false, + ) + } + footer.setClearAllButtonText(button.labelId) + footer.setClearAllButtonDescription(button.accessibilityDescriptionId) + footer.setClearAllButtonClickListener(clearAllNotifications) + } + } + lifecycleScope.launch { viewModel.message.collect { message -> footer.setFooterLabelVisible(message.visible) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt new file mode 100644 index 000000000000..ea5abeff7042 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt @@ -0,0 +1,26 @@ +/* + * 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 com.android.systemui.statusbar.notification.footer.ui.viewmodel + +import android.annotation.StringRes +import com.android.systemui.util.ui.AnimatedValue + +data class FooterButtonViewModel( + @StringRes val labelId: Int, + @StringRes val accessibilityDescriptionId: Int, + val isVisible: AnimatedValue<Boolean>, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index 739048531afb..721bea1086e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -18,21 +18,50 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.ui.AnimatableEvent +import com.android.systemui.util.ui.toAnimatedValueFlow import dagger.Module import dagger.Provides import java.util.Optional import javax.inject.Provider import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** ViewModel for [FooterView]. */ -class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) { - init { - /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode() - } +class FooterViewModel( + activeNotificationsInteractor: ActiveNotificationsInteractor, + seenNotificationsInteractor: SeenNotificationsInteractor, + shadeInteractor: ShadeInteractor, +) { + val clearAllButton: Flow<FooterButtonViewModel> = + activeNotificationsInteractor.hasClearableNotifications + .sample( + combine( + shadeInteractor.isShadeFullyExpanded, + shadeInteractor.isShadeTouchable, + ::Pair + ) + .onStart { emit(Pair(false, false)) } + ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) -> + val shouldAnimate = isShadeFullyExpanded && animationsEnabled + AnimatableEvent(hasClearableNotifications, shouldAnimate) + } + .toAnimatedValueFlow() + .map { visible -> + FooterButtonViewModel( + labelId = R.string.clear_all_notifications_text, + accessibilityDescriptionId = R.string.accessibility_clear_all, + isVisible = visible, + ) + } val message: Flow<FooterMessageViewModel> = seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs -> @@ -49,10 +78,18 @@ object FooterViewModelModule { @Provides @SysUISingleton fun provideOptional( + activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>, seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, + shadeInteractor: Provider<ShadeInteractor>, ): Optional<FooterViewModel> { return if (FooterViewRefactor.isEnabled) { - Optional.of(FooterViewModel(seenNotificationsInteractor.get())) + Optional.of( + FooterViewModel( + activeNotificationsInteractor.get(), + seenNotificationsInteractor.get(), + shadeInteractor.get() + ) + ) } else { Optional.empty() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 9f2b0a6bfc1f..8e824423973f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -204,6 +204,11 @@ class BubbleNotAllowedSuppressor() : override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } +class BubbleAppSuspendedSuppressor : + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") { + override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended +} + class BubbleNoMetadataSuppressor() : VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 2fffd379e6f9..334e08d5a78f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -56,6 +56,14 @@ class NotificationInterruptLogger @Inject constructor( } } + fun logSuspendedAppBubble(entry: NotificationEntry) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + }, { + "No bubble up: notification: app $str1 is suspended" + }) + } + fun logNoBubbleNoMetadata(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 40453800d17f..510086d4892b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -204,6 +204,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + if (entry.getRanking().isSuspended()) { + mLogger.logSuspendedAppBubble(entry); + return false; + } + if (entry.getBubbleMetadata() == null || (entry.getBubbleMetadata().getShortcutId() == null && entry.getBubbleMetadata().getIntent() == null)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index f732e8d74675..16bcd43dd877 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -31,6 +31,9 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti class NotificationInterruptStateProviderWrapper( private val wrapped: NotificationInterruptStateProvider ) : VisualInterruptionDecisionProvider { + init { + VisualInterruptionRefactor.assertInLegacyMode() + } @VisibleForTesting enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index de8863c64873..93fa85d61030 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.CoreStartable import com.android.systemui.statusbar.notification.collection.NotificationEntry /** @@ -26,7 +27,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry * pulsing while the device is dozing), displaying the notification as a bubble, and launching a * full-screen intent for the notification. */ -interface VisualInterruptionDecisionProvider { +interface VisualInterruptionDecisionProvider : CoreStartable { /** * Represents the decision to visually interrupt or not. * @@ -53,7 +54,7 @@ interface VisualInterruptionDecisionProvider { } /** Initializes the provider. */ - fun start() {} + override fun start() {} /** * Adds a [NotificationInterruptSuppressor] that can suppress visual interruptions. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 2b6e1a168b3c..73dd5f6e5fb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -60,6 +60,10 @@ constructor( private val uiEventLogger: UiEventLogger, private val userTracker: UserTracker, ) : VisualInterruptionDecisionProvider { + init { + check(!VisualInterruptionRefactor.isUnexpectedlyInLegacyMode()) + } + interface Loggable { val uiEventId: UiEventEnum? val eventLogData: EventLogData? @@ -155,6 +159,7 @@ constructor( addFilter(PulseLockscreenVisibilityPrivateSuppressor()) addFilter(PulseLowImportanceSuppressor()) addFilter(BubbleNotAllowedSuppressor()) + addFilter(BubbleAppSuspendedSuppressor()) addFilter(BubbleNoMetadataSuppressor()) addFilter(HunGroupAlertBehaviorSuppressor()) addFilter(HunJustLaunchedFsiSuppressor()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 11c65e542bcd..6cb079a22e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2221,8 +2221,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow != null) { mMenuRow.resetMenu(); } - mTranslateAnim = null; } + mTranslateAnim = null; } }); mTranslateAnim = translateAnim; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index ec90a8d6ad59..162e8af47394 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -54,7 +54,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { mContent = findContentView(); mSecondaryView = findSecondaryView(); setVisible(false /* visible */, false /* animate */); - setSecondaryVisible(false /* visible */, false /* animate */); + setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */); setOutlineProvider(null); } @@ -155,15 +155,23 @@ public abstract class StackScrollerDecorView extends ExpandableView { /** * Set the secondary view of this layout to visible. * - * @param visible should the secondary view be visible - * @param animate should the change be animated + * @param visible True if the contents should be visible. + * @param animate True if we should fade to new visibility. + * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a + * parameter that represents whether the animation was cancelled. */ - protected void setSecondaryVisible(boolean visible, boolean animate) { + protected void setSecondaryVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { if (mIsSecondaryVisible != visible) { mSecondaryAnimating = animate; mIsSecondaryVisible = visible; - setViewVisible(mSecondaryView, visible, animate, - (cancelled) -> onSecondaryVisibilityAnimationEnd()); + Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { + onContentVisibilityAnimationEnd(); + if (onAnimationEnded != null) { + onAnimationEnded.accept(cancelled); + } + }; + setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper); } if (!mSecondaryAnimating) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 38d782b43c16..283a5930f930 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -740,7 +740,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFooter(); } - void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { + /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ + public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { + FooterViewRefactor.assertInLegacyMode(); mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications; } @@ -749,7 +751,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mFooterView == null || mController == null) { return; } - // TODO: move this logic to controller, which will invoke updateFooterView directly final boolean showHistory = mController.isHistoryEnabled(); final boolean showDismissView = shouldShowDismissView(); @@ -773,13 +774,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable && !mIsRemoteInputActive; } - /** - * Return whether there are any clearable notifications - */ - boolean hasActiveClearableNotifications(@SelectedRows int selection) { - return mController.hasActiveClearableNotifications(selection); - } - public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -1658,8 +1652,44 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. + * + * TODO(b/308591475): This entire logic can probably be improved as part of the empty shade + * refactor, but for now: + * - if the empty shade is visible, we can assume that the visible notif count is not 0; + * - if the shelf is visible, we can assume there are at least 2 notifications present (this + * is not true for AOD, but this logic refers to the expansion of the shade, where we never + * have the shelf on its own) */ private float getAppearEndPosition() { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + return getAppearEndPositionLegacy(); + } + + int appearPosition = mAmbientState.getStackTopMargin(); + if (mEmptyShadeView.getVisibility() == GONE) { + if (isHeadsUpTransition() + || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { + if (mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; + } + appearPosition += getTopHeadsUpPinnedHeight() + + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); + } else if (mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight(); + } + } else { + appearPosition = mEmptyShadeView.getHeight(); + } + return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); + } + + /** + * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't + * need to know about that, so we want to phase this out with the footer view refactor. + */ + private float getAppearEndPositionLegacy() { + FooterViewRefactor.assertInLegacyMode(); + int appearPosition = mAmbientState.getStackTopMargin(); int visibleNotifCount = mController.getVisibleNotificationCount(); if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { @@ -1698,7 +1728,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // This can't use expansion fraction as that goes only from 0 to 1. Also when // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3 // and that makes translation jump immediately. - float appearEndPosition = getAppearEndPosition(); + float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition() + : getAppearEndPositionLegacy(); float appearStartPosition = getAppearStartPosition(); float hunAppearFraction = (height - appearStartPosition) / (appearEndPosition - appearStartPosition); @@ -4577,13 +4608,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mManageButtonClickListener != null) { mFooterView.setManageButtonClickListener(mManageButtonClickListener); } - mFooterView.setClearAllButtonClickListener(v -> { - if (mFooterClearAllListener != null) { - mFooterClearAllListener.onClearAll(); - } - clearNotifications(ROWS_ALL, true /* closeShade */); - footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); - }); + if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonClickListener(v -> { + if (mFooterClearAllListener != null) { + mFooterClearAllListener.onClearAll(); + } + clearNotifications(ROWS_ALL, true /* closeShade */); + footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); + }); + } if (FooterViewRefactor.isEnabled()) { updateFooter(); } @@ -4599,12 +4632,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable addView(mEmptyShadeView, index); } - void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { + /** Legacy version, should be removed with the footer refactor flag. */ + public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { + FooterViewRefactor.assertInLegacyMode(); + updateEmptyShadeView(visible, areNotificationsHiddenInShade, + mHasFilteredOutSeenNotifications); + } + + /** Trigger an update for the empty shade resources and visibility. */ + public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade, + boolean hasFilteredOutSeenNotifications) { mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); if (areNotificationsHiddenInShade) { updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0); - } else if (mHasFilteredOutSeenNotifications) { + } else if (hasFilteredOutSeenNotifications) { updateEmptyShadeView( R.string.no_unseen_notif_text, R.string.unlock_to_see_notif_text, @@ -4647,9 +4689,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } boolean animate = mIsExpanded && mAnimationsEnabled; mFooterView.setVisible(visible, animate); - mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.showHistory(showHistory); if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } } @@ -4781,10 +4823,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void removeContainerView(View v) { Assert.isMainThread(); removeView(v); - if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { - mController.updateShowEmptyShadeView(); - updateFooter(); - mController.updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + // A notification was removed, and we're not currently showing the empty shade view. + if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { + mController.updateShowEmptyShadeView(); + updateFooter(); + mController.updateImportantForAccessibility(); + } } updateSpeedBumpIndex(); @@ -4793,10 +4838,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void addContainerView(View v) { Assert.isMainThread(); addView(v); - if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { - mController.updateShowEmptyShadeView(); - updateFooter(); - mController.updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + // A notification was added, and we're currently showing the empty shade view. + if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { + mController.updateShowEmptyShadeView(); + updateFooter(); + mController.updateImportantForAccessibility(); + } } updateSpeedBumpIndex(); @@ -4806,7 +4854,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable Assert.isMainThread(); ensureRemovedFromTransientContainer(v); addView(v, index); - if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { + // A notification was added, and we're currently showing the empty shade view. + if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow + && mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); updateFooter(); mController.updateImportantForAccessibility(); @@ -5079,7 +5129,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mEmptyShadeView.getVisibility() == GONE) { return getMinExpansionHeight(); } else { - return getAppearEndPosition(); + return FooterViewRefactor.isEnabled() ? getAppearEndPosition() + : getAppearEndPositionLegacy(); } } @@ -5306,11 +5357,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return viewsToRemove; } + /** Clear all clearable notifications when the user requests it. */ + public void clearAllNotifications() { + clearNotifications(ROWS_ALL, /* closeShade = */ true); + } + /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ - @VisibleForTesting void clearNotifications(@SelectedRows int selection, boolean closeShade) { // Animate-swipe all dismissable notifications, then animate the shade closed final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); @@ -5610,6 +5665,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void setFooterClearAllListener(FooterClearAllListener listener) { + FooterViewRefactor.assertInLegacyMode(); mFooterClearAllListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 3e140a45f8b9..e6315fd159d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -20,7 +20,6 @@ import static android.service.notification.NotificationStats.DISMISSAL_SHADE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; import static com.android.app.animation.Interpolators.STANDARD; - import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -108,7 +107,9 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -223,7 +224,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onViewAttachedToWindow(View v) { mConfigurationController.addCallback(mConfigurationListener); - mZenModeController.addCallback(mZenModeControllerCallback); + if (!FooterViewRefactor.isEnabled()) { + mZenModeController.addCallback(mZenModeControllerCallback); + } final int newBarState = mStatusBarStateController.getState(); if (newBarState != mBarState) { mStateListener.onStateChanged(newBarState); @@ -236,7 +239,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onViewDetachedFromWindow(View v) { mConfigurationController.removeCallback(mConfigurationListener); - mZenModeController.removeCallback(mZenModeControllerCallback); + if (!FooterViewRefactor.isEnabled()) { + mZenModeController.removeCallback(mZenModeControllerCallback); + } mStatusBarStateController.removeCallback(mStateListener); } }; @@ -292,7 +297,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onDensityOrFontScaleChanged() { - updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } mView.reinflateViews(); } @@ -308,7 +315,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateBgColor(); mView.updateDecorViews(); mView.reinflateViews(); - updateShowEmptyShadeView(); + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } updateFooter(); } @@ -357,7 +366,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(), mLockscreenUserManager.isAnyProfilePublicMode()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); - updateImportantForAccessibility(); + if (!FooterViewRefactor.isEnabled()) { + updateImportantForAccessibility(); + } } }; @@ -459,7 +470,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onSnooze(StatusBarNotification sbn, - NotificationSwipeActionHelper.SnoozeOption snoozeOption) { + NotificationSwipeActionHelper.SnoozeOption snoozeOption) { mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } @@ -581,7 +592,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public boolean updateSwipeProgress(View animView, boolean dismissable, - float swipeProgress) { + float swipeProgress) { // Returning true prevents alpha fading. return false; } @@ -673,6 +684,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { UiEventLogger uiEventLogger, NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, + ActiveNotificationsInteractor activeNotificationsInteractor, SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, @@ -747,8 +759,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setClearAllAnimationListener(this::onAnimationEnd); mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - mView.setFooterClearAllListener(() -> - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + if (!FooterViewRefactor.isEnabled()) { + mView.setFooterClearAllListener(() -> + mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + } mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { @Override @@ -840,8 +854,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mViewBinder.bind(mView, this); - collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), - this::onKeyguardTransitionChanged); + if (!FooterViewRefactor.isEnabled()) { + collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), + this::onKeyguardTransitionChanged); + } } private boolean isInVisibleLocation(NotificationEntry entry) { @@ -1031,6 +1047,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public int getVisibleNotificationCount() { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer + // visibility in the refactored code return mNotifStats.getNumActiveNotifs(); } @@ -1074,7 +1092,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setOverScrollAmount(float amount, boolean onTop, boolean animate, - boolean cancelAnimators) { + boolean cancelAnimators) { mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators); } @@ -1124,6 +1142,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setQsFullScreen(boolean fullScreen) { + FooterViewRefactor.assertInLegacyMode(); mView.setQsFullScreen(fullScreen); updateShowEmptyShadeView(); } @@ -1273,7 +1292,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void updateVisibility(boolean visible) { mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - if (mView.getVisibility() == View.VISIBLE) { + // Refactor note: the empty shade's visibility doesn't seem to actually depend on the + // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not + // modeled in the refactored code. + if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) { // Synchronize EmptyShadeView visibility with the parent container. updateShowEmptyShadeView(); updateImportantForAccessibility(); @@ -1288,6 +1310,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { * are true. */ public void updateShowEmptyShadeView() { + FooterViewRefactor.assertInLegacyMode(); + Trace.beginSection("NSSLC.updateShowEmptyShadeView"); final boolean shouldShow = getVisibleNotificationCount() == 0 @@ -1331,6 +1355,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * auto-scrolling in NSSL. */ public void updateImportantForAccessibility() { + FooterViewRefactor.assertInLegacyMode(); if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } else { @@ -1338,16 +1363,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } - /** - * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD - * and false otherwise. - */ - private boolean isInTransitionToKeyguard() { - final int currentState = mStatusBarStateController.getState(); - final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState(); - return (currentState != upcomingState && upcomingState == KEYGUARD); - } - public boolean isShowingEmptyShadeView() { return mView.isEmptyShadeViewVisible(); } @@ -1395,10 +1410,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code return hasNotifications(selection, true /* clearable */); } public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code boolean hasAlertingMatchingClearable = isClearable ? mNotifStats.getHasClearableAlertingNotifs() : mNotifStats.getHasNonClearableAlertingNotifs(); @@ -1437,7 +1456,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { public RemoteInputController.Delegate createDelegate() { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationEntry entry, - boolean remoteInputActive) { + boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); updateFooter(); @@ -1556,7 +1575,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, - @SelectedRows int selectedRows) { + @SelectedRows int selectedRows) { if (selectedRows == ROWS_ALL) { mNotifCollection.dismissAllNotifications( mLockscreenUserManager.getCurrentUserId()); @@ -1648,7 +1667,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Set rounded rect clipping bounds on this view. */ public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, - int bottomRadius) { + int bottomRadius) { mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); } @@ -1678,6 +1697,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @VisibleForTesting void onKeyguardTransitionChanged(TransitionStep transitionStep) { + FooterViewRefactor.assertInLegacyMode(); boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD) && (transitionStep.getFrom().equals(KeyguardState.GONE) || transitionStep.getFrom().equals(KeyguardState.OCCLUDED)); @@ -2003,11 +2023,21 @@ public class NotificationStackScrollLayoutController implements Dumpable { private class NotifStackControllerImpl implements NotifStackController { @Override public void setNotifStats(@NonNull NotifStats notifStats) { + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility + // is handled in the refactored stack. mNotifStats = notifStats; - mView.setHasFilteredOutSeenNotifications( - mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue()); + + if (!FooterViewRefactor.isEnabled()) { + mView.setHasFilteredOutSeenNotifications( + mSeenNotificationsInteractor + .getHasFilteredOutSeenNotifications().getValue()); + } + updateFooter(); - updateShowEmptyShadeView(); + + if (!FooterViewRefactor.isEnabled()) { + updateShowEmptyShadeView(); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index a5b87f088578..4554085c35c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -17,9 +17,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.view.LayoutInflater +import androidx.lifecycle.lifecycleScope import com.android.app.tracing.traceSection +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.nano.MetricsProto import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.reinflateAndBindLatest +import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -36,12 +40,15 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */ class NotificationListViewBinder @Inject constructor( private val viewModel: NotificationListViewModel, + private val metricsLogger: MetricsLogger, private val configuration: ConfigurationState, private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, @@ -56,7 +63,16 @@ constructor( ) { bindShelf(view) bindFooter(view) + bindEmptyShade(view) bindHideList(viewController, viewModel) + + view.repeatWhenAttached { + lifecycleScope.launch { + viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> + view.setImportantForAccessibilityYesNo(isImportantForAccessibility) + } + } + } } private fun bindShelf(parentView: NotificationStackScrollLayout) { @@ -87,7 +103,17 @@ constructor( attachToRoot = false, ) { footerView: FooterView -> traceSection("bind FooterView") { - val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel) + val disposableHandle = + FooterViewBinder.bind( + footerView, + footerViewModel, + clearAllNotifications = { + metricsLogger.action( + MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES + ) + parentView.clearAllNotifications() + }, + ) parentView.setFooterView(footerView) return@reinflateAndBindLatest disposableHandle } @@ -95,4 +121,26 @@ constructor( } } } + + private fun bindEmptyShade( + parentView: NotificationStackScrollLayout, + ) { + parentView.repeatWhenAttached { + lifecycleScope.launch { + combine( + viewModel.shouldShowEmptyShadeView, + viewModel.areNotificationsHiddenInShade, + viewModel.hasFilteredOutSeenNotifications, + ::Triple + ) + .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) -> + parentView.updateEmptyShadeView( + shouldShow, + areNotifsHidden, + hasFilteredNotifs, + ) + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 4f7668060c69..569ae248ad23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -16,10 +16,22 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import java.util.Optional import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.onStart /** ViewModel for the list of notifications. */ class NotificationListViewModel @@ -27,5 +39,78 @@ class NotificationListViewModel constructor( val shelf: NotificationShelfViewModel, val hideListViewModel: HideListViewModel, - val footer: Optional<FooterViewModel> -) + val footer: Optional<FooterViewModel>, + activeNotificationsInteractor: ActiveNotificationsInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + seenNotificationsInteractor: SeenNotificationsInteractor, + shadeInteractor: ShadeInteractor, + zenModeInteractor: ZenModeInteractor, +) { + /** + * We want the NSSL to be unimportant for accessibility when there are no notifications in it + * while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise, + * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL. + * See b/242235264 for more details. + */ + val isImportantForAccessibility: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(true) + } else { + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + keyguardTransitionInteractor.isFinishedInStateWhere { + KeyguardState.lockscreenVisibleInState(it) + } + ) { hasNotifications, isOnKeyguard -> + hasNotifications || !isOnKeyguard + } + .distinctUntilChanged() + } + } + + val shouldShowEmptyShadeView: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + shadeInteractor.isQsFullscreen, + keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart { + emit(false) + }, + keyguardTransitionInteractor + .isFinishedInState(KeyguardState.PRIMARY_BOUNCER) + .onStart { emit(false) } + ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing -> + !hasNotifications && + !isQsFullScreen && + // Hide empty shade view when in transition to AOD. + // That avoids "No Notifications" blinking when transitioning to AOD. + // For more details, see b/228790482. + !transitioningToAOD && + // Don't show any notification content if the bouncer is showing. See + // b/267060171. + !isBouncerShowing + } + .distinctUntilChanged() + } + } + + // TODO(b/308591475): This should be tracked separately by the empty shade. + val areNotificationsHiddenInShade: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + zenModeInteractor.areNotificationsHiddenInShade + } + } + + // TODO(b/308591475): This should be tracked separately by the empty shade. + val hasFilteredOutSeenNotifications: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + seenNotificationsInteractor.hasFilteredOutSeenNotifications + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 296ea884e5c3..09b4dfa31788 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -168,15 +168,11 @@ constructor( // When to limit notifications: on lockscreen with an unexpanded shade. Also, recalculate // when the notification stack has changed internally val limitedNotifications = - combineTransform( - isOnLockscreen, + combine( position, - shadeInteractor.shadeExpansion, interactor.notificationStackChanged.onStart { emit(Unit) }, - ) { isOnLockscreen, position, shadeExpansion, _ -> - if (isOnLockscreen && shadeExpansion == 0f) { - emit(calculateSpace(position.bottom - position.top)) - } + ) { position, _ -> + calculateSpace(position.bottom - position.top) } // When to show unlimited notifications: When the shade is fully expanded and the user is @@ -190,11 +186,14 @@ constructor( emit(-1) } } - - return merge( - limitedNotifications, - unlimitedNotifications, - ) + return isOnLockscreenWithoutShade + .flatMapLatest { isOnLockscreenWithoutShade -> + if (isOnLockscreenWithoutShade) { + limitedNotifications + } else { + unlimitedNotifications + } + } .distinctUntilChanged() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 4cb01e12ea8b..645769cdd586 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -206,7 +206,7 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; @@ -458,7 +458,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final NotificationGutsManager mGutsManager; private final ShadeExpansionStateManager mShadeExpansionStateManager; private final KeyguardViewMediator mKeyguardViewMediator; - protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider; private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; private final FragmentService mFragmentService; @@ -619,7 +619,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { FalsingCollector falsingCollector, BroadcastDispatcher broadcastDispatcher, NotificationGutsManager notificationGutsManager, - NotificationInterruptStateProvider notificationInterruptStateProvider, + VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, ShadeExpansionStateManager shadeExpansionStateManager, KeyguardViewMediator keyguardViewMediator, DisplayMetrics displayMetrics, @@ -726,7 +726,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mGutsManager = notificationGutsManager; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; + mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider; mShadeExpansionStateManager = shadeExpansionStateManager; mKeyguardViewMediator = keyguardViewMediator; mDisplayMetrics = displayMetrics; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index dbee080f238d..2e1a0770757b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -72,7 +72,6 @@ import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorCon import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; @@ -115,7 +114,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final NotificationLockscreenUserManager mLockscreenUserManager; private final com.android.systemui.shade.ShadeController mShadeController; private final KeyguardStateController mKeyguardStateController; - private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final LockPatternUtils mLockPatternUtils; private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback; private final ActivityIntentHelper mActivityIntentHelper; @@ -154,7 +152,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationLockscreenUserManager lockscreenUserManager, ShadeController shadeController, KeyguardStateController keyguardStateController, - NotificationInterruptStateProvider notificationInterruptStateProvider, LockPatternUtils lockPatternUtils, StatusBarRemoteInputCallback remoteInputCallback, ActivityIntentHelper activityIntentHelper, @@ -187,7 +184,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mLockscreenUserManager = lockscreenUserManager; mShadeController = shadeController; mKeyguardStateController = keyguardStateController; - mNotificationInterruptStateProvider = notificationInterruptStateProvider; mLockPatternUtils = lockPatternUtils; mStatusBarRemoteInputCallback = remoteInputCallback; mActivityIntentHelper = activityIntentHelper; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index d66ad5868e65..78f48bb511e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.policy +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AlarmTile import com.android.systemui.qs.tiles.CameraToggleTile @@ -23,8 +25,18 @@ import com.android.systemui.qs.tiles.LocationTile import com.android.systemui.qs.tiles.MicrophoneToggleTile import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper +import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R import dagger.Binds import dagger.Module +import dagger.Provides import dagger.multibindings.IntoMap import dagger.multibindings.StringKey @@ -40,6 +52,42 @@ interface PolicyModule { @StringKey(WorkModeTile.TILE_SPEC) fun bindWorkModeTile(workModeTile: WorkModeTile): QSTileImpl<*> + companion object { + const val FLASHLIGHT_TILE_SPEC = "flashlight" + + /** Inject config */ + @Provides + @IntoMap + @StringKey(FLASHLIGHT_TILE_SPEC) + fun provideFlashlightTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(FLASHLIGHT_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_flashlight_icon_off, + labelRes = R.string.quick_settings_flashlight_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject FlashlightTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(FLASHLIGHT_TILE_SPEC) + fun provideFlashlightTileViewModel( + factory: QSTileViewModelFactory.Static<FlashlightTileModel>, + mapper: FlashlightMapper, + stateInteractor: FlashlightTileDataInteractor, + userActionInteractor: FlashlightTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(FLASHLIGHT_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } + /** Inject FlashlightTile into tileMap in QSModule */ @Binds @IntoMap diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt index 2b51ac5e3187..a7e7dd074a33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.animation import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.core.animation.doOnEnd -import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.doOnEnd @@ -31,7 +30,6 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper -@FlakyTest(bugId = 302149604) class AnimatorTestRuleOrderTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 11c5d3bb27b3..602f3dc29491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -475,6 +475,22 @@ public class AuthControllerTest extends SysuiTestCase { } @Test + public void testOnAuthenticationFailedInvoked_whenBiometricReEnrollRequired() { + showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); + final int modality = BiometricAuthenticator.TYPE_FACE; + mAuthController.onBiometricError(modality, + BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, + 0 /* vendorCode */); + + verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), + mMessageCaptor.capture()); + + assertThat(mModalityCaptor.getValue()).isEqualTo(modality); + assertThat(mMessageCaptor.getValue()).isEqualTo(mContext.getString( + R.string.face_recalibrate_notification_content)); + } + + @Test public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() { testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected( BiometricConstants.BIOMETRIC_PAUSED_REJECTED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 5f0d4d428322..f5b6f14a627c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -36,13 +36,13 @@ import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R @@ -107,7 +107,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsView: UdfpsView @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator - @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @@ -123,47 +122,52 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Before fun setup() { whenever(inflater.inflate(R.layout.udfps_view, null, false)) - .thenReturn(udfpsView) + .thenReturn(udfpsView) whenever(inflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mock(UdfpsBpView::class.java)) + .thenReturn(mock(UdfpsBpView::class.java)) whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mUdfpsKeyguardViewLegacy) + .thenReturn(mUdfpsKeyguardViewLegacy) whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mock(UdfpsFpmEmptyView::class.java)) + .thenReturn(mock(UdfpsFpmEmptyView::class.java)) } private fun withReason( - @ShowReason reason: Int, - isDebuggable: Boolean = false, - block: () -> Unit + @ShowReason reason: Int, + isDebuggable: Boolean = false, + enableDeviceEntryUdfpsRefactor: Boolean = false, + block: () -> Unit, ) { + if (enableDeviceEntryUdfpsRefactor) { + mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + } controllerOverlay = UdfpsControllerOverlay( - context, - inflater, - windowManager, - accessibilityManager, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dialogManager, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - udfpsDisplayMode, - REQUEST_ID, - reason, - controllerCallback, - onTouch, - activityLaunchAnimator, - featureFlags, - primaryBouncerInteractor, - alternateBouncerInteractor, - isDebuggable, - udfpsKeyguardAccessibilityDelegate, - keyguardTransitionInteractor, - mSelectedUserInteractor, + context, + inflater, + windowManager, + accessibilityManager, + statusBarStateController, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dialogManager, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + udfpsDisplayMode, + REQUEST_ID, + reason, + controllerCallback, + onTouch, + activityLaunchAnimator, + primaryBouncerInteractor, + alternateBouncerInteractor, + isDebuggable, + udfpsKeyguardAccessibilityDelegate, + keyguardTransitionInteractor, + mSelectedUserInteractor, ) block() } @@ -185,12 +189,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT) val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT) overlayParams = UdfpsOverlayParams( - sensorBounds, - overlayBounds, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - scaleFactor = 1f, - rotation + sensorBounds, + overlayBounds, + DISPLAY_WIDTH, + DISPLAY_HEIGHT, + scaleFactor = 1f, + rotation ) block() } @@ -200,8 +204,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // ROTATION_0 is the native orientation. Sensor should stay in the top left corner. @@ -218,8 +222,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // ROTATION_180 is not supported. Sensor should stay in the top left corner. @@ -236,8 +240,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Sensor should be in the bottom left corner in ROTATION_90. @@ -254,8 +258,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { withReason(REASON_AUTH_BP) { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Sensor should be in the top right corner in ROTATION_270. @@ -270,7 +274,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private fun showUdfpsOverlay() { val didShow = controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager).addView(eq(controllerOverlay.overlayView), any()) + verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any()) verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode)) verify(udfpsView).animationViewController = any() verify(udfpsView).addView(any()) @@ -278,7 +282,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(didShow).isTrue() assertThat(controllerOverlay.isShowing).isTrue() assertThat(controllerOverlay.isHiding).isFalse() - assertThat(controllerOverlay.overlayView).isNotNull() + assertThat(controllerOverlay.getTouchOverlay()).isNotNull() } @Test @@ -295,14 +299,14 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private fun hideUdfpsOverlay() { val didShow = controllerOverlay.show(udfpsController, overlayParams) - val view = controllerOverlay.overlayView + val view = controllerOverlay.getTouchOverlay() val didHide = controllerOverlay.hide() verify(windowManager).removeView(eq(view)) assertThat(didShow).isTrue() assertThat(didHide).isTrue() - assertThat(controllerOverlay.overlayView).isNull() + assertThat(controllerOverlay.getTouchOverlay()).isNull() assertThat(controllerOverlay.animationViewController).isNull() assertThat(controllerOverlay.isShowing).isFalse() assertThat(controllerOverlay.isHiding).isTrue() @@ -348,8 +352,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() + eq(controllerOverlay.getTouchOverlay()), + layoutParamsCaptor.capture() ) // Layout params should use sensor bounds diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index c8c400de5740..e2cab29c473c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -73,6 +73,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; @@ -286,6 +287,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); initUdfpsController(mOpticalProps); } @@ -304,7 +306,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mDumpManager, mKeyguardUpdateMonitor, - mFeatureFlags, mFalsingManager, mPowerManager, mAccessibilityManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt new file mode 100644 index 000000000000..395d7129bee3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -0,0 +1,253 @@ +/* + * 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 com.android.systemui.bouncer.ui.helper + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.google.common.truth.Truth.assertThat +import java.util.Locale +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@SmallTest +@RunWith(Parameterized::class) +class BouncerSceneLayoutTest : SysuiTestCase() { + + data object Phone : + Device( + name = "phone", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object Tablet : + Device( + name = "tablet", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Horizontally, + ) + data object Folded : + Device( + name = "folded", + width = SizeClass.COMPACT, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + ) + data object Unfolded : + Device( + name = "unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + widthWhenUnnaturallyHeld = SizeClass.MEDIUM, + heightWhenUnnaturallyHeld = SizeClass.MEDIUM, + ) + data object TallerFolded : + Device( + name = "taller folded", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object TallerUnfolded : + Device( + name = "taller unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun testCases() = + listOf( + Phone to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Tablet to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STACKED, + ), + Folded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Unfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STANDARD, + ), + TallerFolded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + TallerUnfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = SIDE_BY_SIDE, + ), + ) + .flatMap { (device, expected) -> + buildList { + // Holding the device in its natural orientation (vertical or horizontal): + add( + TestCase( + device = device, + held = device.naturallyHeld, + expected = expected.layout(heldNaturally = true), + ) + ) + + if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld, + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + + // Holding the device the other way: + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + expected = expected.layout(heldNaturally = false), + ) + ) + + if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + } + } + } + + @Parameterized.Parameter @JvmField var testCase: TestCase? = null + + @Test + fun calculateLayout() { + testCase?.let { nonNullTestCase -> + with(nonNullTestCase) { + assertThat( + calculateLayoutInternal( + width = device.width(whenHeld = held), + height = device.height(whenHeld = held), + isSideBySideSupported = isSideBySideSupported, + ) + ) + .isEqualTo(expected) + } + } + } + + data class TestCase( + val device: Device, + val held: Held, + val expected: BouncerSceneLayout, + val isSideBySideSupported: Boolean = true, + ) { + override fun toString(): String { + return buildString { + append(device.name) + append(" width: ${device.width(held).name.lowercase(Locale.US)}") + append(" height: ${device.height(held).name.lowercase(Locale.US)}") + append(" when held $held") + if (!isSideBySideSupported) { + append(" (side-by-side not supported)") + } + } + } + } + + data class Expected( + val whenNaturallyHeld: BouncerSceneLayout, + val whenUnnaturallyHeld: BouncerSceneLayout, + ) { + fun layout(heldNaturally: Boolean): BouncerSceneLayout { + return if (heldNaturally) { + whenNaturallyHeld + } else { + whenUnnaturallyHeld + } + } + } + + sealed class Device( + val name: String, + private val width: SizeClass, + private val height: SizeClass, + val naturallyHeld: Held, + private val widthWhenUnnaturallyHeld: SizeClass = height, + private val heightWhenUnnaturallyHeld: SizeClass = width, + ) { + fun width(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + width + } else { + widthWhenUnnaturallyHeld + } + } + + fun height(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + height + } else { + heightWhenUnnaturallyHeld + } + } + + private fun isHeldNaturally(whenHeld: Held): Boolean { + return whenHeld == naturallyHeld + } + } + + sealed class Held { + abstract fun flip(): Held + } + data object Vertically : Held() { + override fun flip(): Held { + return Horizontally + } + } + data object Horizontally : Held() { + override fun flip(): Held { + return Vertically + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 125fe680db21..862c39c9d4cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -322,7 +322,6 @@ class PatternBouncerViewModelTest : SysuiTestCase() { xPx = 30f * coordinate.x + 15, yPx = 30f * coordinate.y + 15, containerSizePx = 90, - verticalOffsetPx = 0f, ) } @@ -369,7 +368,6 @@ class PatternBouncerViewModelTest : SysuiTestCase() { xPx = dotSize * coordinate.x + 15f, yPx = dotSize * coordinate.y + 15f, containerSizePx = containerSize, - verticalOffsetPx = 0f, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt index 14ec4d44ab8c..16b2ed633f11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt @@ -124,6 +124,39 @@ class CommunalWidgetDaoTest : SysuiTestCase() { assertThat(widgets()).containsExactly(communalItemRankEntry2, communalWidgetItemEntry2) } + @Test + fun reorderWidget_emitsWidgetsInNewOrder() = + testScope.runTest { + val widgetsToAdd = listOf(widgetInfo1, widgetInfo2) + val widgets = collectLastValue(communalWidgetDao.getWidgets()) + + widgetsToAdd.forEach { + val (widgetId, provider, priority) = it + communalWidgetDao.addWidget( + widgetId = widgetId, + provider = provider, + priority = priority, + ) + } + assertThat(widgets()) + .containsExactly( + communalItemRankEntry1, + communalWidgetItemEntry1, + communalItemRankEntry2, + communalWidgetItemEntry2 + ) + + val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId) + communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder) + assertThat(widgets()) + .containsExactly( + communalItemRankEntry2, + communalWidgetItemEntry2, + communalItemRankEntry1, + communalWidgetItemEntry1 + ) + } + data class FakeWidgetMetadata( val widgetId: Int, val provider: ComponentName, diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 28fae819de98..182712a13174 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -202,6 +202,20 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } @Test + fun reorderWidgets_queryDb() = + testScope.runTest { + userUnlocked(true) + val repository = initCommunalWidgetRepository() + runCurrent() + + val ids = listOf(104, 103, 101) + repository.updateWidgetOrder(ids) + runCurrent() + + verify(communalWidgetDao).updateWidgetOrder(ids) + } + + @Test fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = testScope.runTest { communalEnabled(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index e0567a4c6de5..16cfa2398fd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget -import android.provider.Settings import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -99,24 +98,6 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() = - testScope.runTest { - // Keyguard showing, and tutorial not started. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState( - Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED - ) - - val communalContent by collectLastValue(underTest.communalContent) - - assertThat(communalContent!!).isNotEmpty() - communalContent!!.forEach { model -> - assertThat(model is CommunalContentModel.Tutorial).isTrue() - } - } - - @Test fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() = testScope.runTest { // Keyguard showing, and tutorial completed. @@ -145,12 +126,11 @@ class CommunalInteractorTest : SysuiTestCase() { ) widgetRepository.setCommunalWidgets(widgets) - val communalContent by collectLastValue(underTest.communalContent) + val widgetContent by collectLastValue(underTest.widgetContent) - assertThat(communalContent!!).isNotEmpty() - communalContent!!.forEachIndexed { index, model -> - assertThat((model as CommunalContentModel.Widget).appWidgetId) - .isEqualTo(widgets[index].appWidgetId) + assertThat(widgetContent!!).isNotEmpty() + widgetContent!!.forEachIndexed { index, model -> + assertThat(model.appWidgetId).isEqualTo(widgets[index].appWidgetId) } } @@ -183,48 +163,9 @@ class CommunalInteractorTest : SysuiTestCase() { val targets = listOf(target1, target2, target3) smartspaceRepository.setLockscreenSmartspaceTargets(targets) - val communalContent by collectLastValue(underTest.communalContent) - assertThat(communalContent?.size).isEqualTo(1) - assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target3") - } - - @Test - fun smartspace_smartspaceAndWidgetsAvailable_showSmartspaceAndWidgetContent() = - testScope.runTest { - // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - - // Widgets available. - val widgets = - listOf( - CommunalWidgetContentModel( - appWidgetId = 0, - priority = 30, - providerInfo = mock(), - ), - CommunalWidgetContentModel( - appWidgetId = 1, - priority = 20, - providerInfo = mock(), - ), - ) - widgetRepository.setCommunalWidgets(widgets) - - // Smartspace available. - val target = mock(SmartspaceTarget::class.java) - whenever(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) - - val communalContent by collectLastValue(underTest.communalContent) - - assertThat(communalContent?.size).isEqualTo(3) - assertThat(communalContent?.get(0)?.key).isEqualTo("smartspace_target") - assertThat(communalContent?.get(1)?.key).isEqualTo("widget_0") - assertThat(communalContent?.get(2)?.key).isEqualTo("widget_1") + val smartspaceContent by collectLastValue(underTest.smartspaceContent) + assertThat(smartspaceContent?.size).isEqualTo(1) + assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3") } @Test @@ -236,55 +177,11 @@ class CommunalInteractorTest : SysuiTestCase() { // Media is playing. mediaRepository.mediaPlaying.value = true - val communalContent by collectLastValue(underTest.communalContent) - - assertThat(communalContent?.size).isEqualTo(1) - assertThat(communalContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java) - assertThat(communalContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY) - } - - @Test - fun ordering_smartspaceBeforeUmoBeforeWidgets() = - testScope.runTest { - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - - // Widgets available. - val widgets = - listOf( - CommunalWidgetContentModel( - appWidgetId = 0, - priority = 30, - providerInfo = mock(), - ), - CommunalWidgetContentModel( - appWidgetId = 1, - priority = 20, - providerInfo = mock(), - ), - ) - widgetRepository.setCommunalWidgets(widgets) - - // Smartspace available. - val target = mock(SmartspaceTarget::class.java) - whenever(target.smartspaceTargetId).thenReturn("target") - whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) - whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) - - // Media playing. - mediaRepository.mediaPlaying.value = true + val umoContent by collectLastValue(underTest.umoContent) - val communalContent by collectLastValue(underTest.communalContent) - - // Order is smart space, then UMO, then widget content. - assertThat(communalContent?.size).isEqualTo(4) - assertThat(communalContent?.get(0)) - .isInstanceOf(CommunalContentModel.Smartspace::class.java) - assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java) - assertThat(communalContent?.get(2)) - .isInstanceOf(CommunalContentModel.Widget::class.java) - assertThat(communalContent?.get(3)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + assertThat(umoContent?.size).isEqualTo(1) + assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java) + assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index b16c3520d978..d246f0e49e1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -910,6 +910,20 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { assertATMSAndKeyguardViewMediatorStatesMatch(); } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testStartKeyguardExitAnimation_whenNotInteractive_doesShowAndUpdatesWM() { + // If the exit animation was triggered but the device became non-interactive, make sure + // relock happens + when(mPowerManager.isInteractive()).thenReturn(false); + + startMockKeyguardExitAnimation(); + cancelMockKeyguardExitAnimation(); + + verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null); + assertATMSAndKeyguardViewMediatorStatesMatch(); + } + /** * Interactions with the ActivityTaskManagerService and others are posted to an executor that * doesn't use the testable looper. Use this method to ensure those are run as well. diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt new file mode 100644 index 000000000000..db9e548e74c8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -0,0 +1,86 @@ +package com.android.systemui.qs + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.view.KeyEvent +import android.view.View +import android.widget.Scroller +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class PagedTileLayoutTest : SysuiTestCase() { + + @Mock private lateinit var pageIndicator: PageIndicator + @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener> + + private lateinit var pageTileLayout: TestPagedTileLayout + private lateinit var scroller: Scroller + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + pageTileLayout = TestPagedTileLayout(mContext) + pageTileLayout.setPageIndicator(pageIndicator) + verify(pageIndicator).setOnKeyListener(captor.capture()) + setViewWidth(pageTileLayout, width = PAGE_WIDTH) + scroller = pageTileLayout.mScroller + } + + private fun setViewWidth(view: View, width: Int) { + view.left = 0 + view.right = width + } + + @Test + fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 0 + + sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH) + } + + @Test + fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page + + sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH) + } + + private fun sendUpEvent(keyCode: Int) { + val event = KeyEvent(KeyEvent.ACTION_UP, keyCode) + captor.value.onKey(pageIndicator, keyCode, event) + } + + /** + * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this + * up otherwise would require setting adapter etc + */ + class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) { + + var currentPageIndex: Int = 0 + + override fun getCurrentItem(): Int { + return currentPageIndex + } + } + + companion object { + const val PAGE_WIDTH = 200 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 5c325ae67369..42e27ba12f42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -39,14 +38,11 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class QuickSettingsSceneViewModelTest : SysuiTestCase() { @@ -90,15 +86,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, ) - val authenticationInteractor = utils.authenticationInteractor() - underTest = QuickSettingsSceneViewModel( - deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, notifications = utils.notificationsPlaceholderViewModel(), @@ -106,32 +95,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun onContentClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test fun destinationsNotCustomizing() = testScope.runTest { val destinations by collectLastValue(underTest.destinationScenes) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index ba8a66637e8a..03878b7bcf45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -147,6 +147,7 @@ import com.android.systemui.statusbar.notification.ConversationNotificationManag import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -335,6 +336,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private ActiveNotificationsInteractor mActiveNotificationsInteractor; @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; @@ -709,6 +711,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor, mActivityStarter, mSharedNotificationContainerInteractor, + mActiveNotificationsInteractor, mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor, new ResourcesSplitShadeStateController(), @@ -783,6 +786,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardFaceAuthInteractor, mShadeRepository, mShadeInteractor, + mActiveNotificationsInteractor, mJavaAdapter, mCastController, new ResourcesSplitShadeStateController() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 26b84e372d5c..bff47f1e3927 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -80,6 +80,8 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; @@ -178,6 +180,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { protected SysuiStatusBarStateController mStatusBarStateController; protected ShadeInteractor mShadeInteractor; + protected ActiveNotificationsInteractor mActiveNotificationsInteractor; + protected Handler mMainHandler; protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; @@ -290,6 +294,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ) ); + mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(new ActiveNotificationListRepository()); + KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -362,6 +369,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mock(KeyguardFaceAuthInteractor.class), mShadeRepository, mShadeInteractor, + mActiveNotificationsInteractor, new JavaAdapter(mTestScope.getBackgroundScope()), mCastController, splitShadeStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt index ea7c06865001..ffde6015c127 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingOb import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.mockito.mock +import com.android.systemui.shade.data.repository.FakeShadeRepository import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -61,6 +62,7 @@ class DragDownHelperTest : SysuiTestCase() { falsingCollector, dragDownloadCallback, naturalScrollingSettingObserver, + FakeShadeRepository(), mContext, ).also { it.expandCallback = expandCallback diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 428574bb15f8..fa5fad06b671 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfte import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING @@ -57,6 +58,7 @@ class StackCoordinatorTest : SysuiTestCase() { @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor + @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor @Mock private lateinit var stackController: NotifStackController @Mock private lateinit var section: NotifSection @@ -75,6 +77,7 @@ class StackCoordinatorTest : SysuiTestCase() { groupExpansionManagerImpl, notificationIconAreaController, renderListInteractor, + activeNotificationsInteractor, ) coordinator.attach(pipeline) afterRenderListListener = withArgCaptor { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt new file mode 100644 index 000000000000..4ab3cd49b297 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt @@ -0,0 +1,129 @@ +/* + * 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 com.android.systemui.statusbar.notification.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.collectLastValue +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Test + +@SmallTest +class ActiveNotificationsInteractorTest : SysuiTestCase() { + + @Component(modules = [SysUITestModule::class]) + @SysUISingleton + interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> { + val activeNotificationListRepository: ActiveNotificationListRepository + + @Component.Factory + interface Factory { + fun create(@BindsInstance test: SysuiTestCase): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this) + + @Test + fun testAreAnyNotificationsPresent_isTrue() = + testComponent.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(2) + runCurrent() + + assertThat(areAnyNotificationsPresent).isTrue() + assertThat(underTest.areAnyNotificationsPresentValue).isTrue() + } + + @Test + fun testAreAnyNotificationsPresent_isFalse() = + testComponent.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(0) + runCurrent() + + assertThat(areAnyNotificationsPresent).isFalse() + assertThat(underTest.areAnyNotificationsPresentValue).isFalse() + } + + @Test + fun testHasClearableNotifications_whenHasClearableAlertingNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasClearableSilentNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasNoClearableNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index a64ac674a91c..22c5bae93489 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -114,9 +114,46 @@ public class FooterViewTest extends SysuiTestCase { } @Test + public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { + int resId = R.string.clear_all_notifications_text; + mView.setClearAllButtonText(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getText().toString()).contains("Clear all"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonText(resId); + mView.setClearAllButtonText(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { + int resId = R.string.accessibility_clear_all; + mView.setClearAllButtonDescription(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getContentDescription().toString()).contains("Clear all notifications"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonDescription(resId); + mView.setClearAllButtonDescription(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test public void testSetMessageString_resourceOnlyFetchedOnce() { - mView.setMessageString(R.string.unlock_to_see_notif_text); - verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text)); + int resId = R.string.unlock_to_see_notif_text; + mView.setMessageString(resId); + verify(mSpyContext).getString(eq(resId)); clearInvocations(mSpyContext); @@ -124,22 +161,23 @@ public class FooterViewTest extends SysuiTestCase { .getText().toString()).contains("Unlock"); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageString(R.string.unlock_to_see_notif_text); - mView.setMessageString(R.string.unlock_to_see_notif_text); + mView.setMessageString(resId); + mView.setMessageString(resId); verify(mSpyContext, never()).getString(anyInt()); } @Test public void testSetMessageIcon_resourceOnlyFetchedOnce() { - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed)); + int resId = R.drawable.ic_friction_lock_closed; + mView.setMessageIcon(resId); + verify(mSpyContext).getDrawable(eq(resId)); clearInvocations(mSpyContext); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); + mView.setMessageIcon(resId); + mView.setMessageIcon(resId); verify(mSpyContext, never()).getDrawable(anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 57a7c3c7e2bf..94dcf7a18514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -18,37 +18,222 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +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.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest +import dagger.BindsInstance +import dagger.Component +import java.util.Optional +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class FooterViewModelTest : SysuiTestCase() { - private val repository = ActiveNotificationListRepository() - private val interactor = SeenNotificationsInteractor(repository) - private val underTest = FooterViewModel(interactor) + private lateinit var footerViewModel: FooterViewModel - @Test - fun testMessageVisible_whenFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ActivatableNotificationViewModelModule::class, + FooterViewModelModule::class, + HeadlessSystemUserModeModule::class, + ] + ) + interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> { + val activeNotificationListRepository: ActiveNotificationListRepository + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val shadeRepository: FakeShadeRepository + val powerRepository: FakePowerRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() - repository.hasFilteredOutSeenNotifications.value = true + private val testComponent: TestComponent = + DaggerFooterViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ) + ) - assertThat(message?.visible).isTrue() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) + + // The underTest in the component is Optional, because that matches the provider we + // currently have for the footer view model. + footerViewModel = testComponent.underTest.get() } @Test - fun testMessageVisible_whenNoFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + fun testMessageVisible_whenFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) - repository.hasFilteredOutSeenNotifications.value = false + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true - assertThat(message?.visible).isFalse() - } + assertThat(message?.visible).isTrue() + } + + @Test + fun testMessageVisible_whenNoFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false + + assertThat(message?.visible).isFalse() + } + + @Test + fun testClearAllButtonVisible_whenHasClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isTrue() + } + + @Test + fun testClearAllButtonVisible_whenHasNoClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isFalse() + } + + @Test + fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(1f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should animate + assertThat(button?.isVisible?.isAnimating).isTrue() + } + + @Test + fun testClearAllButtonAnimating_whenShadeNotExpanded() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is collapsed + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(0f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should not animate + assertThat(button?.isVisible?.isAnimating).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index 034103598bb0..360a373711d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -26,10 +26,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.runTest import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository -import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.notification.shared.byIsAmbient import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply import com.android.systemui.statusbar.notification.shared.byIsPulsing diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index c2a1519f85dd..e264fc07489a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -43,10 +43,10 @@ import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.runCurrent import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository -import com.android.systemui.statusbar.notification.shared.activeNotificationModel import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 3e331a6cff2a..0a9bac91004a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -999,11 +999,25 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse(); } + @Test + public void shouldNotBubbleUp_suspended() { + assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createSuspendedBubble())) + .isFalse(); + } + + private NotificationEntry createSuspendedBubble() { + return createBubble(null, null, true); + } + private NotificationEntry createBubble() { - return createBubble(null, null); + return createBubble(null, null, false); } private NotificationEntry createBubble(String groupKey, Integer groupAlert) { + return createBubble(groupKey, groupAlert, false); + } + + private NotificationEntry createBubble(String groupKey, Integer groupAlert, Boolean suspended) { Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, new Intent().setPackage(mContext.getPackageName()), @@ -1031,6 +1045,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setNotification(n) .setImportance(IMPORTANCE_HIGH) .setCanBubble(true) + .setSuspended(suspended) .build(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index acc5cea0c8f9..1c7fd565c289 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE @@ -35,6 +36,10 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { + init { + setFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + } + override val provider by lazy { NotificationInterruptStateProviderWrapper( NotificationInterruptStateProviderImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 9e7df5f4a9f5..df6f0d716577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK @@ -28,6 +29,10 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { + init { + setFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR) + } + override val provider by lazy { VisualInterruptionDecisionProviderImpl( ambientDisplayConfiguration, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 5dcb6c9e9527..7babff5e0b2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -44,6 +44,7 @@ import android.graphics.drawable.Icon import android.hardware.display.FakeAmbientDisplayConfiguration import android.os.Looper import android.os.PowerManager +import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON @@ -83,10 +84,15 @@ import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Assert.assertEquals import org.junit.Before +import org.junit.Rule import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { + @JvmField + @Rule + val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT) + private val fakeLogBuffer = LogBuffer( name = "FakeLog", @@ -595,6 +601,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testShouldNotBubble_bubbleAppSuspended() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { packageSuspended = true }) + assertNoEventsLogged() + } + + @Test fun testShouldNotFsi_noFullScreenIntent() { forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt index ca105f3e52ea..16c5c8a98253 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt @@ -15,7 +15,6 @@ package com.android.systemui.statusbar.notification.shared -import android.graphics.drawable.Icon import com.google.common.truth.Correspondence val byKey: Correspondence<ActiveNotificationModel, String> = @@ -38,30 +37,3 @@ val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> = ) val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> = Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of") - -fun activeNotificationModel( - key: String, - groupKey: String? = null, - isAmbient: Boolean = false, - isRowDismissed: Boolean = false, - isSilent: Boolean = false, - isLastMessageFromReply: Boolean = false, - isSuppressedFromStatusBar: Boolean = false, - isPulsing: Boolean = false, - aodIcon: Icon? = null, - shelfIcon: Icon? = null, - statusBarIcon: Icon? = null, -) = - ActiveNotificationModel( - key = key, - groupKey = groupKey, - isAmbient = isAmbient, - isRowDismissed = isRowDismissed, - isSilent = isSilent, - isLastMessageFromReply = isLastMessageFromReply, - isSuppressedFromStatusBar = isSuppressedFromStatusBar, - isPulsing = isPulsing, - aodIcon = aodIcon, - shelfIcon = shelfIcon, - statusBarIcon = statusBarIcon, - ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 590389035e92..ff5c02622e4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.when; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import android.content.res.Resources; import android.metrics.LogMaker; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -53,7 +52,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; @@ -74,7 +72,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; @@ -84,17 +81,15 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -170,8 +165,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; + private final ActiveNotificationListRepository mActiveNotificationsRepository = + new ActiveNotificationListRepository(); + + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationsRepository); + private final SeenNotificationsInteractor mSeenNotificationsInteractor = - new SeenNotificationsInteractor(new ActiveNotificationListRepository()); + new SeenNotificationsInteractor(mActiveNotificationsRepository); private NotificationStackScrollLayoutController mController; @@ -701,6 +702,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mUiEventLogger, mRemoteInputManager, mVisibilityLocationProviderDelegator, + mActiveNotificationsInteractor, mSeenNotificationsInteractor, mViewBinder, mShadeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index 46e8453a6d1d..ac20683b4f49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -34,9 +34,11 @@ import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.unfold.TestUnfoldTransitionProvider import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl -import com.android.systemui.util.animation.FakeAnimationStatusRepository +import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import java.time.Duration +import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.TestScope @@ -47,8 +49,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations -import java.time.Duration -import java.util.Optional @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt new file mode 100644 index 000000000000..f00abc9c3f63 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -0,0 +1,313 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.stack.ui.viewmodel + +import android.app.NotificationManager.Policy +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.common.domain.CommonDomainLayerModule +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.res.R +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule +import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule +import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository +import com.android.systemui.unfold.UnfoldTransitionModule +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationListViewModelTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ActivatableNotificationViewModelModule::class, + CommonDomainLayerModule::class, + FooterViewModelModule::class, + HeadlessSystemUserModeModule::class, + UnfoldTransitionModule.Bindings::class, + ] + ) + interface TestComponent : SysUITestComponent<NotificationListViewModel> { + val activeNotificationListRepository: ActiveNotificationListRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val shadeRepository: FakeShadeRepository + val zenModeRepository: FakeZenModeRepository + val configurationController: FakeConfigurationController + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerNotificationListViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule() + ) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) + } + + @Test + fun testIsImportantForAccessibility_falseWhenNoNotifs() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + // AND has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + testScope.runCurrent() + + // THEN not important + assertThat(important).isFalse() + } + + @Test + fun testIsImportantForAccessibility_trueWhenNotifs() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + // AND has notifs + activeNotificationListRepository.setActiveNotifs(count = 2) + runCurrent() + + // THEN is important + assertThat(important).isTrue() + } + + @Test + fun testIsImportantForAccessibility_trueWhenNotKeyguard() = + testComponent.runTest { + val important by collectLastValue(underTest.isImportantForAccessibility) + + // WHEN not on lockscreen + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope, + ) + // AND has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + runCurrent() + + // THEN is still important + assertThat(important).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_trueWhenNoNotifs() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + runCurrent() + + // THEN should show + assertThat(shouldShow).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenNotifs() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has notifs + activeNotificationListRepository.setActiveNotifs(count = 2) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND quick settings are expanded + shadeRepository.legacyQsFullscreen.value = true + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND quick settings are expanded + shadeRepository.setQsExpansion(1f) + // AND split shade is enabled + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationController.notifyConfigurationChanged() + runCurrent() + + // THEN should show + assertThat(shouldShow).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND transitioning to AOD + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + ) + ) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() = + testComponent.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND is on bouncer + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope, + ) + runCurrent() + + // THEN should not show + assertThat(shouldShow).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_true() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isTrue() + } + + @Test + fun testAreNotificationsHiddenInShade_false() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + zenModeRepository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + zenModeRepository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testHasFilteredOutSeenNotifications_true() = + testComponent.runTest { + val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true + runCurrent() + + assertThat(hasFilteredNotifs).isTrue() + } + + @Test + fun testHasFilteredOutSeenNotifications_false() = + testComponent.runTest { + val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false + runCurrent() + + assertThat(hasFilteredNotifs).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index ebdff082b7b3..4b1c7e8faa38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; @@ -27,13 +25,9 @@ import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; @@ -49,8 +43,6 @@ import static java.util.Collections.emptySet; import android.app.ActivityManager; import android.app.IWallpaperManager; -import android.app.Notification; -import android.app.NotificationChannel; import android.app.WallpaperManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; @@ -158,13 +150,13 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -219,7 +211,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private CentralSurfacesImpl mCentralSurfaces; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; - private TestableNotificationInterruptStateProviderImpl mNotificationInterruptStateProvider; + private VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider; @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @@ -362,24 +354,25 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFakeGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON); - mNotificationInterruptStateProvider = - new TestableNotificationInterruptStateProviderImpl( - mPowerManager, - mAmbientDisplayConfiguration, - mStatusBarStateController, - mKeyguardStateController, - mBatteryController, - mHeadsUpManager, - mock(NotificationInterruptLogger.class), - new Handler(TestableLooper.get(this).getLooper()), - mock(NotifPipelineFlags.class), - mock(KeyguardNotificationVisibilityProvider.class), - mock(UiEventLogger.class), - mUserTracker, - mDeviceProvisionedController, - mFakeSystemClock, - mFakeGlobalSettings, - mFakeEventLog); + mVisualInterruptionDecisionProvider = + new NotificationInterruptStateProviderWrapper( + new TestableNotificationInterruptStateProviderImpl( + mPowerManager, + mAmbientDisplayConfiguration, + mStatusBarStateController, + mKeyguardStateController, + mBatteryController, + mHeadsUpManager, + mock(NotificationInterruptLogger.class), + new Handler(TestableLooper.get(this).getLooper()), + mock(NotifPipelineFlags.class), + mock(KeyguardNotificationVisibilityProvider.class), + mock(UiEventLogger.class), + mUserTracker, + mDeviceProvisionedController, + mFakeSystemClock, + mFakeGlobalSettings, + mFakeEventLog)); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -482,7 +475,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new FalsingCollectorFake(), mBroadcastDispatcher, mNotificationGutsManager, - mNotificationInterruptStateProvider, + mVisualInterruptionDecisionProvider, new ShadeExpansionStateManager(), mKeyguardViewMediator, new DisplayMetrics(), @@ -693,92 +686,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception { - when(mPowerManager.isScreenOn()).thenReturn(true); - when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mStatusBarStateController.isDreaming()).thenReturn(false); - - Notification n = new Notification.Builder(getContext(), "a") - .setGroup("a") - .setGroupSummary(true) - .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(n) - .setImportance(IMPORTANCE_HIGH) - .build(); - - assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); - } - - @Test - public void testShouldHeadsUp_suppressedGroupSummary() throws Exception { - when(mPowerManager.isScreenOn()).thenReturn(true); - when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mStatusBarStateController.isDreaming()).thenReturn(false); - - Notification n = new Notification.Builder(getContext(), "a") - .setGroup("a") - .setGroupSummary(true) - .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(n) - .setImportance(IMPORTANCE_HIGH) - .build(); - - assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); - } - - @Test - public void testShouldHeadsUp_suppressedHeadsUp() throws Exception { - when(mPowerManager.isScreenOn()).thenReturn(true); - when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mStatusBarStateController.isDreaming()).thenReturn(false); - - Notification n = new Notification.Builder(getContext(), "a").build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setChannel(new NotificationChannel("id", null, IMPORTANCE_HIGH)) - .setNotification(n) - .setImportance(IMPORTANCE_HIGH) - .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK) - .build(); - - assertFalse(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); - } - - @Test - public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception { - when(mPowerManager.isScreenOn()).thenReturn(true); - when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false); - when(mStatusBarStateController.isDreaming()).thenReturn(false); - - Notification n = new Notification.Builder(getContext(), "a").build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(n) - .setImportance(IMPORTANCE_HIGH) - .build(); - - assertTrue(mNotificationInterruptStateProvider.shouldHeadsUp(entry)); - } - - @Test public void testDump_DoesNotCrash() { mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index d1423e10ce79..6cc4e44116ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -88,7 +88,6 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFul import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository; import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; @@ -139,8 +138,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock - private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - @Mock private Handler mHandler; @Mock private BubblesManager mBubblesManager; @@ -246,7 +243,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardStateController, - mNotificationInterruptStateProvider, mock(LockPatternUtils.class), mock(StatusBarRemoteInputCallback.class), mActivityIntentHelper, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index 99e62eec890c..dbb106264ecd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -128,8 +128,7 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) repository.zenMode.value = Settings.Global.ZEN_MODE_OFF runCurrent() @@ -141,8 +140,7 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) + repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS runCurrent() @@ -154,19 +152,10 @@ class ZenModeInteractorTest : SysuiTestCase() { testComponent.runTest { val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) - repository.consolidatedNotificationPolicy.value = - policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.setSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS runCurrent() assertThat(hidden).isTrue() } } - -fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) = - Policy( - /* priorityCategories = */ 0, - /* priorityCallSenders = */ 0, - /* priorityMessageSenders = */ 0, - /* suppressedVisualEffects = */ suppressedVisualEffects - ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index aa5f987b22a8..df7609c544a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -78,6 +78,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; import android.util.SparseArray; +import android.view.Display; import android.view.IWindowManager; import android.view.View; import android.view.ViewTreeObserver; @@ -337,6 +338,8 @@ public class BubblesTest extends SysuiTestCase { private NotifPipelineFlags mNotifPipelineFlags; @Mock private Icon mAppBubbleIcon; + @Mock + private Display mDefaultDisplay; private final SceneTestUtils mUtils = new SceneTestUtils(this); private final TestScope mTestScope = mUtils.getTestScope(); @@ -378,6 +381,7 @@ public class BubblesTest extends SysuiTestCase { when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); when(mNotificationShadeWindowView.getViewTreeObserver()) .thenReturn(mock(ViewTreeObserver.class)); + when(mWindowManager.getDefaultDisplay()).thenReturn(mDefaultDisplay); FakeDeviceProvisioningRepository deviceProvisioningRepository = diff --git a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/InstanceIdSequenceFake.kt index 6fbe3ada2406..aae270d2b849 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/InstanceIdSequenceFake.kt @@ -19,14 +19,10 @@ package com.android.systemui import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceIdSequence -/** - * Fake [InstanceId] generator. - */ +/** Fake [InstanceId] generator. */ class InstanceIdSequenceFake(instanceIdMax: Int) : InstanceIdSequence(instanceIdMax) { - /** - * Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated. - */ + /** Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated. */ var lastInstanceId = -1 private set @@ -38,4 +34,4 @@ class InstanceIdSequenceFake(instanceIdMax: Int) : InstanceIdSequence(instanceId } return newInstanceIdInternal(lastInstanceId) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index d0c1267c9af0..3724291571d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -15,6 +15,7 @@ */ package com.android.systemui +import android.content.ContentResolver import android.content.Context import android.content.res.Resources import android.testing.TestableContext @@ -80,6 +81,9 @@ interface SysUITestModule { test.fakeBroadcastDispatcher @Provides + fun provideContentResolver(context: Context): ContentResolver = context.contentResolver + + @Provides fun provideBaseShadeInteractor( sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 37a4f6181921..f57ace9bfdc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -55,7 +55,9 @@ import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock import com.android.wm.shell.bubbles.Bubbles import dagger.Binds @@ -100,6 +102,10 @@ data class TestMocksModule( @get:Provides val keyguardViewController: KeyguardViewController = mock(), @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(), @get:Provides val sysuiState: SysUiState = mock(), + @get:Provides + val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> = + Optional.empty(), + @get:Provides val zenModeController: ZenModeController = mock(), // log buffers @get:[Provides BroadcastDispatcherLog] diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index 3aee889d55f4..faacce64b2e4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt @@ -65,7 +65,7 @@ object CommunalInteractorFactory { widgetRepository, mediaRepository, smartspaceRepository, - withDeps.communalTutorialInteractor, + withDeps.keyguardInteractor, appWidgetHost, editWidgetsActivityStarter, ), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt index 36f088214153..8c653a535e29 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt @@ -26,12 +26,14 @@ import com.android.systemui.shade.data.repository.FakeShadeDataLayerModule import com.android.systemui.statusbar.data.FakeStatusBarDataLayerModule import com.android.systemui.telephony.data.FakeTelephonyDataLayerModule import com.android.systemui.user.data.FakeUserDataLayerModule +import com.android.systemui.util.animation.data.FakeAnimationUtilDataLayerModule import dagger.Module @Module( includes = [ FakeAccessibilityDataLayerModule::class, + FakeAnimationUtilDataLayerModule::class, FakeAuthenticationDataLayerModule::class, FakeBouncerDataLayerModule::class, FakeCommonDataLayerModule::class, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index d3744d55c55d..4068e408f0bd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -167,6 +167,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _isKeyguardOccluded.value = isOccluded } + fun setKeyguardUnlocked(isUnlocked: Boolean) { + _isKeyguardUnlocked.value = isUnlocked + } + override fun setIsDozing(isDozing: Boolean) { _isDozing.value = isDozing } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QsEventLoggerFake.kt index 40aa2607dc94..baccc6f6962f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QsEventLoggerFake.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt new file mode 100644 index 000000000000..1cb2587e4e99 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -0,0 +1,27 @@ +/* + * 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 com.android.systemui.qs + +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.InstanceIdSequenceFake +import com.android.systemui.kosmos.Kosmos + +val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by + Kosmos.Fixture { InstanceIdSequenceFake(0) } +val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() } +val Kosmos.qsEventLogger: QsEventLoggerFake by + Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt new file mode 100644 index 000000000000..97e9b519b022 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/flashlight/FlashlightTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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 com.android.systemui.qs.tiles.impl.flashlight + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.policy.PolicyModule + +val Kosmos.qsFlashlightTileConfig by + Kosmos.Fixture { PolicyModule.provideFlashlightTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt new file mode 100644 index 000000000000..9851b0ef9e94 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt @@ -0,0 +1,48 @@ +/* + * 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 com.android.systemui.statusbar.notification.data.model + +import android.graphics.drawable.Icon +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel + +/** Simple ActiveNotificationModel builder for use in tests. */ +fun activeNotificationModel( + key: String, + groupKey: String? = null, + isAmbient: Boolean = false, + isRowDismissed: Boolean = false, + isSilent: Boolean = false, + isLastMessageFromReply: Boolean = false, + isSuppressedFromStatusBar: Boolean = false, + isPulsing: Boolean = false, + aodIcon: Icon? = null, + shelfIcon: Icon? = null, + statusBarIcon: Icon? = null, +) = + ActiveNotificationModel( + key = key, + groupKey = groupKey, + isAmbient = isAmbient, + isRowDismissed = isRowDismissed, + isSilent = isSilent, + isLastMessageFromReply = isLastMessageFromReply, + isSuppressedFromStatusBar = isSuppressedFromStatusBar, + isPulsing = isPulsing, + aodIcon = aodIcon, + shelfIcon = shelfIcon, + statusBarIcon = statusBarIcon, + ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt new file mode 100644 index 000000000000..cb1ba206d110 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepositoryExt.kt @@ -0,0 +1,30 @@ +/* + * 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 com.android.systemui.statusbar.notification.data.repository + +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel + +/** + * Make the repository hold [count] active notifications for testing. The keys of the notifications + * are "0", "1", ..., (count - 1).toString(). + */ +fun ActiveNotificationListRepository.setActiveNotifs(count: Int) { + this.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { repeat(count) { i -> addEntry(activeNotificationModel(key = i.toString())) } } + .build() +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt index 405993073b68..c4d78678e59a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.policy.data.repository -import android.app.NotificationManager +import android.app.NotificationManager.Policy import android.provider.Settings import com.android.systemui.dagger.SysUISingleton import dagger.Binds @@ -27,14 +27,24 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class FakeZenModeRepository @Inject constructor() : ZenModeRepository { override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF) - override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> = + override val consolidatedNotificationPolicy: MutableStateFlow<Policy?> = MutableStateFlow( - NotificationManager.Policy( + Policy( /* priorityCategories = */ 0, /* priorityCallSenders = */ 0, /* priorityMessageSenders = */ 0, ) ) + + fun setSuppressedVisualEffects(suppressedVisualEffects: Int) { + consolidatedNotificationPolicy.value = + Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + /* suppressedVisualEffects = */ suppressedVisualEffects, + ) + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt new file mode 100644 index 000000000000..f7830d9655a7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/FakeAnimationUtilDataLayerModule.kt @@ -0,0 +1,23 @@ +/* + * 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 com.android.systemui.util.animation.data + +import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepositoryModule +import dagger.Module + +@Module(includes = [FakeAnimationStatusRepositoryModule::class]) +object FakeAnimationUtilDataLayerModule diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt index e72235ca508f..ca6628b7f357 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/FakeAnimationStatusRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/animation/data/repository/FakeAnimationStatusRepository.kt @@ -11,15 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.util.animation +package com.android.systemui.util.animation.data.repository -import com.android.systemui.util.animation.data.repository.AnimationStatusRepository +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -class FakeAnimationStatusRepository : AnimationStatusRepository { +class FakeAnimationStatusRepository @Inject constructor() : AnimationStatusRepository { // Replay 1 element as real repository always emits current status as a first element private val animationsEnabled: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1) @@ -30,3 +32,8 @@ class FakeAnimationStatusRepository : AnimationStatusRepository { animationsEnabled.tryEmit(enabled) } } + +@Module +interface FakeAnimationStatusRepositoryModule { + @Binds fun bindFake(fake: FakeAnimationStatusRepository): AnimationStatusRepository +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java index 1d315798d647..f02f06c056bd 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java @@ -18,7 +18,6 @@ package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -32,7 +31,7 @@ import java.lang.annotation.Target; * * @hide */ -@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Target({FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) public @interface RavenwoodKeep { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java new file mode 100644 index 000000000000..784727410188 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepPartialClass.java @@ -0,0 +1,34 @@ +/* + * 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.ravenwood.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * + * @hide + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeepPartialClass { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java new file mode 100644 index 000000000000..eeebee985e4a --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepStaticInitializer.java @@ -0,0 +1,33 @@ +/* + * 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.ravenwood.annotation; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * @hide + */ +@Target(TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeepStaticInitializer { +} diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index 28639d4960a4..48e93280f73f 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -17,12 +17,14 @@ class android.util.MapCollections stubclass class android.util.Log stubclass class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host class android.util.LogPrinter stubclass +class android.util.LocalLog stubclass # String Manipulation class android.util.Printer stubclass class android.util.PrintStreamPrinter stubclass class android.util.PrintWriterPrinter stubclass class android.util.StringBuilderPrinter stubclass +class android.util.IndentingPrintWriter stubclass # Properties class android.util.Property stubclass @@ -76,6 +78,7 @@ class android.util.Patterns stubclass class android.util.UtilConfig stubclass # Internals +class com.android.internal.util.FastPrintWriter stubclass class com.android.internal.util.GrowingArrayUtils stubclass class com.android.internal.util.LineBreakBufferedWriter stubclass class com.android.internal.util.Preconditions stubclass diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index df44fde2ed72..a791f682fecf 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -2,6 +2,12 @@ com.android.internal.util.ArrayUtils +android.util.DataUnit +android.util.EventLog +android.util.IntArray +android.util.LongArray +android.util.Slog +android.util.TimeUtils android.util.Xml android.os.Binder diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt index 4b07ef6e35a8..f842f33bc95b 100644 --- a/ravenwood/ravenwood-standard-options.txt +++ b/ravenwood/ravenwood-standard-options.txt @@ -18,6 +18,9 @@ --keep-annotation android.ravenwood.annotation.RavenwoodKeep +--keep-annotation + android.ravenwood.annotation.RavenwoodKeepPartialClass + --keep-class-annotation android.ravenwood.annotation.RavenwoodKeepWholeClass @@ -35,3 +38,6 @@ --class-load-hook-annotation android.ravenwood.annotation.RavenwoodClassLoadHook + +--keep-static-initializer-annotation + android.ravenwood.annotation.RavenwoodKeepStaticInitializer diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 45ca7260bbe7..d31b1efafcc4 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -69,6 +69,7 @@ import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.gestures.GestureUtils; /** @@ -261,8 +262,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } mDelegatingState = new DelegatingState(); - mDetectingState = new DetectingState(context); - mViewportDraggingState = new ViewportDraggingState(); + mDetectingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture() + ? new DetectingStateWithMultiFinger(context) + : new DetectingState(context); + mViewportDraggingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture() + ? new ViewportDraggingStateWithMultiFinger() + : new ViewportDraggingState(); mPanningScalingState = new PanningScalingState(context); mSinglePanningState = new SinglePanningState(context); mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper; @@ -634,6 +639,62 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + final class ViewportDraggingStateWithMultiFinger extends ViewportDraggingState { + // LINT.IfChange(viewport_dragging_state_with_multi_finger) + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) + throws GestureException { + final int action = event.getActionMasked(); + switch (action) { + case ACTION_POINTER_DOWN: { + clearAndTransitToPanningScalingState(); + } + break; + case ACTION_MOVE: { + if (event.getPointerCount() > 2) { + throw new GestureException("Should have one pointer down."); + } + final float eventX = event.getX(); + final float eventY = event.getY(); + if (mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, eventX, eventY)) { + mFullScreenMagnificationController.setCenter(mDisplayId, eventX, eventY, + /* animate */ mLastMoveOutsideMagnifiedRegion, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mLastMoveOutsideMagnifiedRegion = false; + } else { + mLastMoveOutsideMagnifiedRegion = true; + } + } + break; + + case ACTION_UP: + case ACTION_CANCEL: { + // If mScaleToRecoverAfterDraggingEnd >= 1.0, the dragging state is triggered + // by zoom in temporary, and the magnifier needs to recover to original scale + // after exiting dragging state. + // Otherwise, the magnifier should be disabled. + if (mScaleToRecoverAfterDraggingEnd >= 1.0f) { + zoomToScale(mScaleToRecoverAfterDraggingEnd, event.getX(), + event.getY()); + } else { + zoomOff(); + } + clear(); + mScaleToRecoverAfterDraggingEnd = Float.NaN; + transitionTo(mDetectingState); + } + break; + + case ACTION_DOWN: { + throw new GestureException( + "Unexpected event type: " + MotionEvent.actionToString(action)); + } + } + } + // LINT.ThenChange(:viewport_dragging_state) + } + /** * This class handles motion events when the event dispatcher has * determined that the user is performing a single-finger drag of the @@ -643,17 +704,18 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * of the finger, and any part of the screen is reachable without lifting the finger. * This makes it the preferable mode for tasks like reading text spanning full screen width. */ - final class ViewportDraggingState implements State { + class ViewportDraggingState implements State { /** * The cached scale for recovering after dragging ends. * If the scale >= 1.0, the magnifier needs to recover to scale. * Otherwise, the magnifier should be disabled. */ - @VisibleForTesting float mScaleToRecoverAfterDraggingEnd = Float.NaN; + @VisibleForTesting protected float mScaleToRecoverAfterDraggingEnd = Float.NaN; - private boolean mLastMoveOutsideMagnifiedRegion; + protected boolean mLastMoveOutsideMagnifiedRegion; + // LINT.IfChange(viewport_dragging_state) @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) throws GestureException { @@ -706,6 +768,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } } + // LINT.ThenChange(:viewport_dragging_state_with_multi_finger) private boolean isAlwaysOnMagnificationEnabled() { return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled(); @@ -732,7 +795,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH ? mFullScreenMagnificationController.getScale(mDisplayId) : Float.NaN; } - private void clearAndTransitToPanningScalingState() { + protected void clearAndTransitToPanningScalingState() { final float scaleToRecovery = mScaleToRecoverAfterDraggingEnd; clear(); mScaleToRecoverAfterDraggingEnd = scaleToRecovery; @@ -791,36 +854,220 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + final class DetectingStateWithMultiFinger extends DetectingState { + // A flag set to true when two fingers have touched down. + // Used to indicate what next finger action should be. + private boolean mIsTwoFingerCountReached = false; + // A tap counts when two fingers are down and up once. + private int mCompletedTapCount = 0; + DetectingStateWithMultiFinger(Context context) { + super(context); + } + + // LINT.IfChange(detecting_state_with_multi_finger) + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cacheDelayedMotionEvent(event, rawEvent, policyFlags); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mLastDetectingDownEventTime = event.getDownTime(); + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + + mFirstPointerDownLocation.set(event.getX(), event.getY()); + + if (!mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, event.getX(), event.getY())) { + + transitionToDelegatingStateAndClear(); + + } else if (isMultiTapTriggered(2 /* taps */)) { + + // 3tap and hold + afterLongTapTimeoutTransitionToDraggingState(event); + + } else if (isTapOutOfDistanceSlop()) { + + transitionToDelegatingStateAndClear(); + + } else if (mDetectSingleFingerTripleTap + || mDetectTwoFingerTripleTap + // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay + // to ensure reachability of + // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) + || isActivated()) { + + afterMultiTapTimeoutTransitionToDelegatingState(); + + } else { + + // Delegate pending events without delay + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_POINTER_DOWN: { + mIsTwoFingerCountReached = mDetectTwoFingerTripleTap + && event.getPointerCount() == 2; + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + + if (isActivated() && event.getPointerCount() == 2) { + storePointerDownLocation(mSecondPointerDownLocation, event); + mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, + ViewConfiguration.getTapTimeout()); + } else if (mIsTwoFingerCountReached) { + // Placing two-finger triple-taps behind isActivated to avoid + // blocking panning scaling state + if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) { + // 3tap and hold + afterLongTapTimeoutTransitionToDraggingState(event); + } else { + afterMultiTapTimeoutTransitionToDelegatingState(); + } + } else { + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_POINTER_UP: { + // If it is a two-finger gesture, do not transition to the delegating state + // to ensure the reachability of + // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP) + if (!mIsTwoFingerCountReached) { + transitionToDelegatingStateAndClear(); + } + } + break; + case ACTION_MOVE: { + if (isFingerDown() + && distance(mLastDown, /* move */ event) > mSwipeMinDistance) { + // Swipe detected - transition immediately + + // For convenience, viewport dragging takes precedence + // over insta-delegating on 3tap&swipe + // (which is a rare combo to be used aside from magnification) + if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { + transitionToViewportDraggingStateAndClear(event); + } else if (isActivated() && event.getPointerCount() == 2) { + if (mIsSinglePanningEnabled + && overscrollState(event, mFirstPointerDownLocation) + == OVERSCROLL_VERTICAL_EDGE) { + transitionToDelegatingStateAndClear(); + } + //Primary pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) + && event.getPointerCount() == 2) { + // Placing two-finger triple-taps behind isActivated to avoid + // blocking panning scaling state + transitionToViewportDraggingStateAndClear(event); + } else if (mIsSinglePanningEnabled + && isActivated() + && event.getPointerCount() == 1) { + if (overscrollState(event, mFirstPointerDownLocation) + == OVERSCROLL_VERTICAL_EDGE) { + transitionToDelegatingStateAndClear(); + } + transitToSinglePanningStateAndClear(); + } else { + transitionToDelegatingStateAndClear(); + } + } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation) + && distanceClosestPointerToPoint( + mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) { + //Second pointer is swiping, so transit to PanningScalingState + transitToPanningScalingStateAndClear(); + } + } + break; + case ACTION_UP: { + + mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + + if (!mFullScreenMagnificationController.magnificationRegionContains( + mDisplayId, event.getX(), event.getY())) { + transitionToDelegatingStateAndClear(); + + } else if (isMultiTapTriggered(3 /* taps */)) { + onTripleTap(/* up */ event); + + } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) { + onTripleTap(event); + + } else if ( + // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP + isFingerDown() + //TODO long tap should never happen here + && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay) + || (distance(mLastDown, mLastUp) >= mSwipeMinDistance)) + // If it is a two-finger but not reach 3 tap, do not transition to the + // delegating state to ensure the reachability of the triple tap + && mCompletedTapCount == 0) { + transitionToDelegatingStateAndClear(); + + } + } + break; + } + } + // LINT.ThenChange(:detecting_state) + + @Override + public void clear() { + mCompletedTapCount = 0; + setShortcutTriggered(false); + removePendingDelayedMessages(); + clearDelayedMotionEvents(); + mFirstPointerDownLocation.set(Float.NaN, Float.NaN); + mSecondPointerDownLocation.set(Float.NaN, Float.NaN); + } + + private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) { + if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) { + mCompletedTapCount++; + mIsTwoFingerCountReached = false; + } + return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount; + } + + void transitionToDelegatingStateAndClear() { + mCompletedTapCount = 0; + transitionTo(mDelegatingState); + sendDelayedMotionEvents(); + removePendingDelayedMessages(); + mFirstPointerDownLocation.set(Float.NaN, Float.NaN); + mSecondPointerDownLocation.set(Float.NaN, Float.NaN); + } + } + /** * This class handles motion events when the event dispatch has not yet * determined what the user is doing. It watches for various tap events. */ - final class DetectingState implements State, Handler.Callback { + class DetectingState implements State, Handler.Callback { - private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; - private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; - private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3; + protected static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1; + protected static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; + protected static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3; final int mLongTapMinDelay; final int mSwipeMinDistance; final int mMultiTapMaxDelay; final int mMultiTapMaxDistance; - private MotionEventInfo mDelayedEventQueue; - MotionEvent mLastDown; - private MotionEvent mPreLastDown; - private MotionEvent mLastUp; - private MotionEvent mPreLastUp; - private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected MotionEventInfo mDelayedEventQueue; + protected MotionEvent mLastDown; + protected MotionEvent mPreLastDown; + protected MotionEvent mLastUp; + protected MotionEvent mPreLastUp; - private long mLastDetectingDownEventTime; + protected PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN); + protected long mLastDetectingDownEventTime; @VisibleForTesting boolean mShortcutTriggered; @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this); - private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN); - DetectingState(Context context) { mLongTapMinDelay = ViewConfiguration.getLongPressTimeout(); mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout() @@ -855,6 +1102,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return true; } + // LINT.IfChange(detecting_state) @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { cacheDelayedMotionEvent(event, rawEvent, policyFlags); @@ -969,23 +1217,24 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH break; } } + // LINT.ThenChange(:detecting_state_with_multi_finger) - private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { + protected void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) { final int index = event.getActionIndex(); pointerDownLocation.set(event.getX(index), event.getY(index)); } - private boolean pointerDownValid(PointF pointerDownLocation) { + protected boolean pointerDownValid(PointF pointerDownLocation) { return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN( pointerDownLocation.y)); } - private void transitToPanningScalingStateAndClear() { + protected void transitToPanningScalingStateAndClear() { transitionTo(mPanningScalingState); clear(); } - private void transitToSinglePanningStateAndClear() { + protected void transitToSinglePanningStateAndClear() { transitionTo(mSinglePanningState); clear(); } @@ -1016,7 +1265,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return mLastDown != null; } - private long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) { + protected long timeBetween(@Nullable MotionEvent a, @Nullable MotionEvent b) { if (a == null && b == null) return 0; return abs(timeOf(a) - timeOf(b)); } @@ -1061,13 +1310,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mSecondPointerDownLocation.set(Float.NaN, Float.NaN); } - private void removePendingDelayedMessages() { + protected void removePendingDelayedMessages() { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE); } - private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, + protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (event.getActionMasked() == ACTION_DOWN) { mPreLastDown = mLastDown; @@ -1090,7 +1339,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } - private void sendDelayedMotionEvents() { + protected void sendDelayedMotionEvents() { if (mDelayedEventQueue == null) { return; } @@ -1112,7 +1361,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } while (mDelayedEventQueue != null); } - private void clearDelayedMotionEvents() { + protected void clearDelayedMotionEvents() { while (mDelayedEventQueue != null) { MotionEventInfo info = mDelayedEventQueue; mDelayedEventQueue = info.mNext; @@ -1136,7 +1385,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * 1. direct three tap gesture * 2. one tap while shortcut triggered (it counts as two taps). */ - private void onTripleTap(MotionEvent up) { + protected void onTripleTap(MotionEvent up) { if (DEBUG_DETECTING) { Slog.i(mLogTag, "onTripleTap(); delayed: " + MotionEventInfo.toString(mDelayedEventQueue)); @@ -1156,7 +1405,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH clear(); } - private boolean isActivated() { + protected boolean isActivated() { return mFullScreenMagnificationController.isActivated(mDisplayId); } @@ -1167,6 +1416,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Only log the 3tap and hold event if (!shortcutTriggered) { + // TODO:(b/309534286): Add metrics for two-finger triple-tap and fix + // the log two-finger bug before enabling the flag // Triple tap and hold also belongs to triple tap event final boolean enabled = !isActivated(); mMagnificationLogger.logMagnificationTripleTap(enabled); diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index eeaa423b1aef..c111ec3213d9 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -49,7 +49,6 @@ import com.android.server.input.InputManagerInternal; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Map; import java.util.Objects; @@ -83,14 +82,6 @@ class InputController { @interface PhysType { } - /** - * The maximum length of a device name (in bytes in UTF-8 encoding). - * - * This limitation comes directly from uinput. - * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h - */ - private static final int DEVICE_NAME_MAX_LENGTH = 80; - final Object mLock = new Object(); /* Token -> file descriptor associations. */ @@ -138,25 +129,17 @@ class InputController { } } - void createDpad(@NonNull String deviceName, - int vendorId, - int productId, - @NonNull IBinder deviceToken, - int displayId) { + void createDpad(@NonNull String deviceName, int vendorId, int productId, + @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException { final String phys = createPhys(PHYS_TYPE_DPAD); - try { - createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId, + createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId, productId, deviceToken, displayId, phys, () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys)); - } catch (DeviceCreationException e) { - throw new RuntimeException( - "Failed to create virtual dpad device '" + deviceName + "'.", e); - } } void createKeyboard(@NonNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, @NonNull String languageTag, - @NonNull String layoutType) { + @NonNull String layoutType) throws DeviceCreationException { final String phys = createPhys(PHYS_TYPE_KEYBOARD); mInputManagerInternal.addKeyboardLayoutAssociation(phys, languageTag, layoutType); @@ -166,66 +149,42 @@ class InputController { () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys)); } catch (DeviceCreationException e) { mInputManagerInternal.removeKeyboardLayoutAssociation(phys); - throw new RuntimeException( - "Failed to create virtual keyboard device '" + deviceName + "'.", e); + throw e; } } - void createMouse(@NonNull String deviceName, - int vendorId, - int productId, - @NonNull IBinder deviceToken, - int displayId) { + void createMouse(@NonNull String deviceName, int vendorId, int productId, + @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException { final String phys = createPhys(PHYS_TYPE_MOUSE); - try { - createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, - deviceToken, displayId, phys, - () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); - } catch (DeviceCreationException e) { - throw new RuntimeException( - "Failed to create virtual mouse device: '" + deviceName + "'.", e); - } + createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, + deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); } - void createTouchscreen(@NonNull String deviceName, - int vendorId, - int productId, - @NonNull IBinder deviceToken, - int displayId, - int height, - int width) { + void createTouchscreen(@NonNull String deviceName, int vendorId, int productId, + @NonNull IBinder deviceToken, int displayId, int height, int width) + throws DeviceCreationException { final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN); - try { - createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, - productId, deviceToken, displayId, phys, - () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, - phys, height, width)); - } catch (DeviceCreationException e) { - throw new RuntimeException( - "Failed to create virtual touchscreen device '" + deviceName + "'.", e); - } + createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, + productId, deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys, + height, width)); } - void createNavigationTouchpad( - @NonNull String deviceName, - int vendorId, - int productId, - @NonNull IBinder deviceToken, - int displayId, - int touchpadHeight, - int touchpadWidth) { + void createNavigationTouchpad(@NonNull String deviceName, int vendorId, int productId, + @NonNull IBinder deviceToken, int displayId, int height, int width) + throws DeviceCreationException { final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD); mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE); try { createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName, vendorId, productId, deviceToken, displayId, phys, () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, - phys, touchpadHeight, touchpadWidth)); + phys, height, width)); } catch (DeviceCreationException e) { mInputManagerInternal.unsetTypeAssociation(phys); - throw new RuntimeException( - "Failed to create virtual navigation touchpad device '" + deviceName + "'.", e); + throw e; } } @@ -234,10 +193,10 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not unregister input device for given token"); + Slog.w(TAG, "Could not unregister input device for given token."); + } else { + closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); } - closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); } } @@ -326,21 +285,11 @@ class InputController { } /** - * Validates a device name by checking length and whether a device with the same name - * already exists. Throws exceptions if the validation fails. + * Validates a device name by checking whether a device with the same name already exists. * @param deviceName The name of the device to be validated * @throws DeviceCreationException if {@code deviceName} is not valid. */ private void validateDeviceName(String deviceName) throws DeviceCreationException { - // Comparison is greater or equal because the device name must fit into a const char* - // including the \0-terminator. Therefore the actual number of bytes that can be used - // for device name is DEVICE_NAME_MAX_LENGTH - 1 - if (deviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) { - throw new DeviceCreationException( - "Input device name exceeds maximum length of " + DEVICE_NAME_MAX_LENGTH - + "bytes: " + deviceName); - } - synchronized (mLock) { for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) { if (mInputDeviceDescriptors.valueAt(i).mName.equals(deviceName)) { @@ -365,8 +314,7 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send key event to input device for given token"); + return false; } return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(), event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); @@ -378,8 +326,7 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send key event to input device for given token"); + return false; } return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(), event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); @@ -391,13 +338,12 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send button event to input device for given token"); + return false; } if (inputDeviceDescriptor.getDisplayId() != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - throw new IllegalStateException( - "Display id associated with this mouse is not currently targetable"); + mInputManagerInternal.setVirtualMousePointerDisplayId( + inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(), event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); @@ -409,8 +355,7 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send touch event to input device for given token"); + return false; } return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(), event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), @@ -424,13 +369,12 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send relative event to input device for given token"); + return false; } if (inputDeviceDescriptor.getDisplayId() != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - throw new IllegalStateException( - "Display id associated with this mouse is not currently targetable"); + mInputManagerInternal.setVirtualMousePointerDisplayId( + inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(), event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos()); @@ -442,13 +386,12 @@ class InputController { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( token); if (inputDeviceDescriptor == null) { - throw new IllegalArgumentException( - "Could not send scroll event to input device for given token"); + return false; } if (inputDeviceDescriptor.getDisplayId() != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - throw new IllegalStateException( - "Display id associated with this mouse is not currently targetable"); + mInputManagerInternal.setVirtualMousePointerDisplayId( + inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(), event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos()); @@ -465,8 +408,8 @@ class InputController { } if (inputDeviceDescriptor.getDisplayId() != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - throw new IllegalStateException( - "Display id associated with this mouse is not currently targetable"); + mInputManagerInternal.setVirtualMousePointerDisplayId( + inputDeviceDescriptor.getDisplayId()); } return LocalServices.getService(InputManagerInternal.class).getCursorPosition(); } @@ -758,13 +701,19 @@ class InputController { } /** An internal exception that is thrown to indicate an error when opening a virtual device. */ - private static class DeviceCreationException extends Exception { + static class DeviceCreationException extends Exception { + DeviceCreationException() { + super(); + } DeviceCreationException(String message) { super(message); } - DeviceCreationException(String message, Exception cause) { + DeviceCreationException(String message, Throwable cause) { super(message, cause); } + DeviceCreationException(Throwable cause) { + super(cause); + } } /** diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 118943df1bf6..e6bfeb79fafb 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -694,6 +694,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(), config.getProductId(), deviceToken, getTargetDisplayIdForInput(config.getAssociatedDisplayId())); + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); } finally { Binder.restoreCallingIdentity(ident); } @@ -705,15 +707,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.createVirtualKeyboard_enforcePermission(); Objects.requireNonNull(config); checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); - synchronized (mVirtualDeviceLock) { - mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag()); - } final long ident = Binder.clearCallingIdentity(); try { mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(), config.getProductId(), deviceToken, getTargetDisplayIdForInput(config.getAssociatedDisplayId()), config.getLanguageTag(), config.getLayoutType()); + synchronized (mVirtualDeviceLock) { + mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag()); + } + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); } finally { Binder.restoreCallingIdentity(ident); } @@ -729,6 +733,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub try { mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(), config.getProductId(), deviceToken, config.getAssociatedDisplayId()); + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); } finally { Binder.restoreCallingIdentity(ident); } @@ -741,19 +747,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.createVirtualTouchscreen_enforcePermission(); Objects.requireNonNull(config); checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); - int screenHeight = config.getHeight(); - int screenWidth = config.getWidth(); - if (screenHeight <= 0 || screenWidth <= 0) { - throw new IllegalArgumentException( - "Cannot create a virtual touchscreen, screen dimensions must be positive. Got: " - + "(" + screenWidth + ", " + screenHeight + ")"); - } - final long ident = Binder.clearCallingIdentity(); try { mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(), config.getProductId(), deviceToken, config.getAssociatedDisplayId(), - screenHeight, screenWidth); + config.getHeight(), config.getWidth()); + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); } finally { Binder.restoreCallingIdentity(ident); } @@ -766,21 +766,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.createVirtualNavigationTouchpad_enforcePermission(); Objects.requireNonNull(config); checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); - int touchpadHeight = config.getHeight(); - int touchpadWidth = config.getWidth(); - if (touchpadHeight <= 0 || touchpadWidth <= 0) { - throw new IllegalArgumentException( - "Cannot create a virtual navigation touchpad, touchpad dimensions must be positive." - + " Got: (" + touchpadHeight + ", " + touchpadWidth + ")"); - } - final long ident = Binder.clearCallingIdentity(); try { mInputController.createNavigationTouchpad( config.getInputDeviceName(), config.getVendorId(), config.getProductId(), deviceToken, getTargetDisplayIdForInput(config.getAssociatedDisplayId()), - touchpadHeight, touchpadWidth); + config.getHeight(), config.getWidth()); + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java index 202f68bdeb4a..a570d0989134 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.companion.virtual.camera.IVirtualCameraCallback; import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtualcamera.Format; import android.companion.virtualcamera.IVirtualCameraService; import android.companion.virtualcamera.SupportedStreamConfiguration; import android.companion.virtualcamera.VirtualCameraConfiguration; +import android.graphics.ImageFormat; import android.os.RemoteException; import android.view.Surface; @@ -85,10 +87,14 @@ public final class VirtualCameraConversionUtil { SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration(); supportedConfig.height = stream.getHeight(); supportedConfig.width = stream.getWidth(); - supportedConfig.pixelFormat = stream.getFormat(); + supportedConfig.pixelFormat = convertFormat(stream.getFormat()); return supportedConfig; } + private static int convertFormat(int format) { + return format == ImageFormat.YUV_420_888 ? Format.YUV_420_888 : Format.UNKNOWN; + } + private VirtualCameraConversionUtil() { } } diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 8cd5ce1f4ff8..ce1a8756a849 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -158,6 +158,10 @@ public final class PinnerService extends SystemService { @GuardedBy("this") private ArraySet<Integer> mPinKeys; + // Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want + // them to be responsive to dynamic flag changes for experimentation. + private static final String DEVICE_CONFIG_NAMESPACE_ANON_SIZE = + DeviceConfig.NAMESPACE_RUNTIME_NATIVE; private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size"; private static final long DEFAULT_ANON_SIZE = SystemProperties.getLong("pinner.pin_shared_anon_size", 0); @@ -188,11 +192,11 @@ public final class PinnerService extends SystemService { } }; - private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = + private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigAnonSizeListener = new DeviceConfig.OnPropertiesChangedListener() { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT.equals(properties.getNamespace()) + if (DEVICE_CONFIG_NAMESPACE_ANON_SIZE.equals(properties.getNamespace()) && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) { refreshPinAnonConfig(); } @@ -246,9 +250,9 @@ public final class PinnerService extends SystemService { registerUserSetupCompleteListener(); mDeviceConfigInterface.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DEVICE_CONFIG_NAMESPACE_ANON_SIZE, new HandlerExecutor(mPinnerHandler), - mDeviceConfigListener); + mDeviceConfigAnonSizeListener); } @Override @@ -733,7 +737,7 @@ public final class PinnerService extends SystemService { private void refreshPinAnonConfig() { long newPinAnonSize = mDeviceConfigInterface.getLong( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE); newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE)); diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java index 0d423d8a0a62..2ba3a1d751d0 100644 --- a/services/core/java/com/android/server/VpnManagerService.java +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -33,7 +33,6 @@ import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.INetd; import android.net.IVpnManager; -import android.net.LinkProperties; import android.net.Network; import android.net.NetworkStack; import android.net.UnderlyingNetworkInfo; @@ -437,16 +436,9 @@ public class VpnManagerService extends IVpnManager.Stub { throw new UnsupportedOperationException("Legacy VPN is deprecated"); } int user = UserHandle.getUserId(mDeps.getCallingUid()); - // Note that if the caller is not system (uid >= Process.FIRST_APPLICATION_UID), - // the code might not work well since getActiveNetwork might return null if the uid is - // blocked by NetworkPolicyManagerService. - final LinkProperties egress = mCm.getLinkProperties(mCm.getActiveNetwork()); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } synchronized (mVpns) { throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress); + mVpns.get(user).startLegacyVpn(profile); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 9716cf69015c..3ce91c8a2b7b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1076,6 +1076,16 @@ final class ActivityManagerConstants extends ContentObserver { public boolean APP_PROFILER_PSS_PROFILING_DISABLED; + /** + * The modifier used to adjust PSS thresholds in OomAdjuster when RSS is collected instead. + */ + static final String KEY_PSS_TO_RSS_THRESHOLD_MODIFIER = + "pss_to_rss_threshold_modifier"; + + private final float mDefaultPssToRssThresholdModifier; + + public float PSS_TO_RSS_THRESHOLD_MODIFIER; + private final OnPropertiesChangedListener mOnDeviceConfigChangedListener = new OnPropertiesChangedListener() { @Override @@ -1254,6 +1264,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_DISABLE_APP_PROFILER_PSS_PROFILING: updateDisableAppProfilerPssProfiling(); break; + case KEY_PSS_TO_RSS_THRESHOLD_MODIFIER: + updatePssToRssThresholdModifier(); + break; default: updateFGSPermissionEnforcementFlagsIfNecessary(name); break; @@ -1339,6 +1352,10 @@ final class ActivityManagerConstants extends ContentObserver { mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean( R.bool.config_am_disablePssProfiling); APP_PROFILER_PSS_PROFILING_DISABLED = mDefaultDisableAppProfilerPssProfiling; + + mDefaultPssToRssThresholdModifier = context.getResources().getFloat( + com.android.internal.R.dimen.config_am_pssToRssThresholdModifier); + PSS_TO_RSS_THRESHOLD_MODIFIER = mDefaultPssToRssThresholdModifier; } public void start(ContentResolver resolver) { @@ -2051,6 +2068,12 @@ final class ActivityManagerConstants extends ContentObserver { mDefaultDisableAppProfilerPssProfiling); } + private void updatePssToRssThresholdModifier() { + PSS_TO_RSS_THRESHOLD_MODIFIER = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_PSS_TO_RSS_THRESHOLD_MODIFIER, + mDefaultPssToRssThresholdModifier); + } + @NeverCompile // Avoid size overhead of debugging code. void dump(PrintWriter pw) { pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) " @@ -2242,6 +2265,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING); pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED); + pw.print(" "); pw.print(KEY_PSS_TO_RSS_THRESHOLD_MODIFIER); + pw.print("="); pw.println(PSS_TO_RSS_THRESHOLD_MODIFIER); + pw.println(); if (mOverrideMaxCachedProcesses >= 0) { pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 5dd0a3f58217..55b161ad6348 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -71,6 +71,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_PROCESSES = DEBUG_ALL || false; static final boolean DEBUG_PROVIDER = DEBUG_ALL || false; static final boolean DEBUG_PSS = DEBUG_ALL || false; + static final boolean DEBUG_RSS = DEBUG_ALL || false; static final boolean DEBUG_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false; @@ -91,6 +92,7 @@ class ActivityManagerDebugConfig { ? "_ProcessObservers" : ""; static final String POSTFIX_PROCESSES = (APPEND_CATEGORY_NAME) ? "_Processes" : ""; static final String POSTFIX_PSS = (APPEND_CATEGORY_NAME) ? "_Pss" : ""; + static final String POSTFIX_RSS = (APPEND_CATEGORY_NAME) ? "_Rss" : ""; static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : ""; static final String POSTFIX_SERVICE_EXECUTING = (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b2a794865a48..c64fb2366dd6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -327,6 +327,7 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.FactoryTest; import android.os.FileUtils; +import android.os.Flags; import android.os.Handler; import android.os.IBinder; import android.os.IDeviceIdentifiersPolicyService; @@ -6563,7 +6564,7 @@ public class ActivityManagerService extends IActivityManager.Stub | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); final Intent intent = new Intent(); - intent.setData(uri); + intent.setData(ContentProvider.maybeAddUserId(uri, userId)); intent.setFlags(modeFlags); final NeededUriGrants needed = mUgmInternal.checkGrantUriPermissionFromIntent(intent, @@ -8563,6 +8564,12 @@ public class ActivityManagerService extends IActivityManager.Stub // If the processes' memory has increased by more than 1% of the total memory, // or 10 MB, whichever is greater, then the processes' are eligible to be killed. final long totalMemoryInKb = getTotalMemory() / 1000; + + // This threshold should be applicable to both PSS and RSS because the value is absolute + // and represents an increase in process memory relative to its own previous state. + // + // TODO(b/296454553): Tune this value during the flag rollout process if more processes + // seem to be getting killed than before. final long memoryGrowthThreshold = Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD); mProcessList.forEachLruProcessesLOSP(false, proc -> { @@ -8575,24 +8582,34 @@ public class ActivityManagerService extends IActivityManager.Stub if (state.isNotCachedSinceIdle()) { if (setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE && setProcState <= ActivityManager.PROCESS_STATE_SERVICE) { - final long initialIdlePss, lastPss, lastSwapPss; + final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss; synchronized (mAppProfiler.mProfilerLock) { - initialIdlePss = pr.getInitialIdlePss(); - lastPss = pr.getLastPss(); + initialIdlePssOrRss = pr.getInitialIdlePssOrRss(); + lastPssOrRss = !Flags.removeAppProfilerPssCollection() + ? pr.getLastPss() : pr.getLastRss(); lastSwapPss = pr.getLastSwapPss(); } - if (doKilling && initialIdlePss != 0 - && lastPss > (initialIdlePss * 3 / 2) - && lastPss > (initialIdlePss + memoryGrowthThreshold)) { + if (doKilling && initialIdlePssOrRss != 0 + && lastPssOrRss > (initialIdlePssOrRss * 3 / 2) + && lastPssOrRss > (initialIdlePssOrRss + memoryGrowthThreshold)) { final StringBuilder sb2 = new StringBuilder(128); sb2.append("Kill"); sb2.append(proc.processName); - sb2.append(" in idle maint: pss="); - sb2.append(lastPss); - sb2.append(", swapPss="); - sb2.append(lastSwapPss); - sb2.append(", initialPss="); - sb2.append(initialIdlePss); + if (!Flags.removeAppProfilerPssCollection()) { + sb2.append(" in idle maint: pss="); + } else { + sb2.append(" in idle maint: rss="); + } + sb2.append(lastPssOrRss); + + if (!Flags.removeAppProfilerPssCollection()) { + sb2.append(", swapPss="); + sb2.append(lastSwapPss); + sb2.append(", initialPss="); + } else { + sb2.append(", initialRss="); + } + sb2.append(initialIdlePssOrRss); sb2.append(", period="); TimeUtils.formatDuration(timeSinceLastIdle, sb2); sb2.append(", lowRamPeriod="); @@ -8600,8 +8617,9 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.wtfQuiet(TAG, sb2.toString()); mHandler.post(() -> { synchronized (ActivityManagerService.this) { - proc.killLocked("idle maint (pss " + lastPss - + " from " + initialIdlePss + ")", + proc.killLocked(!Flags.removeAppProfilerPssCollection() + ? "idle maint (pss " : "idle maint (rss " + lastPssOrRss + + " from " + initialIdlePssOrRss + ")", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE, true); @@ -8613,7 +8631,7 @@ public class ActivityManagerService extends IActivityManager.Stub && setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) { state.setNotCachedSinceIdle(true); synchronized (mAppProfiler.mProfilerLock) { - pr.setInitialIdlePss(0); + pr.setInitialIdlePssOrRss(0); mAppProfiler.updateNextPssTimeLPf( state.getSetProcState(), proc.mProfile, now, true); } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 9d8f979e883c..f3b2ef34ad6d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -552,7 +552,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mAsync = true; } else if (opt.equals("--splashscreen-show-icon")) { mShowSplashScreen = true; - } else if (opt.equals("--dismiss-keyguard-if-insecure")) { + } else if (opt.equals("--dismiss-keyguard-if-insecure") + || opt.equals("--dismiss-keyguard")) { mDismissKeyguardIfInsecure = true; } else { return false; diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 3cf4332349d7..2e0aec973bd6 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -28,7 +28,9 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MOD import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RSS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RSS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_ADJ; @@ -64,6 +66,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Debug; +import android.os.Flags; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -125,6 +128,7 @@ public class AppProfiler { private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; static final String TAG_PSS = TAG + POSTFIX_PSS; + static final String TAG_RSS = TAG + POSTFIX_RSS; static final String TAG_OOM_ADJ = ActivityManagerService.TAG_OOM_ADJ; @@ -183,10 +187,10 @@ public class AppProfiler { private volatile long mPssDeferralTime = 0; /** - * Processes we want to collect PSS data from. + * Processes we want to collect PSS or RSS data from. */ @GuardedBy("mProfilerLock") - private final ArrayList<ProcessProfileRecord> mPendingPssProfiles = new ArrayList<>(); + private final ArrayList<ProcessProfileRecord> mPendingPssOrRssProfiles = new ArrayList<>(); /** * Depth of overlapping activity-start PSS deferral notes @@ -200,18 +204,18 @@ public class AppProfiler { private long mLastFullPssTime = SystemClock.uptimeMillis(); /** - * If set, the next time we collect PSS data we should do a full collection - * with data from native processes and the kernel. + * If set, the next time we collect PSS or RSS data we should do a full collection with data + * from native processes and the kernel. */ @GuardedBy("mProfilerLock") - private boolean mFullPssPending = false; + private boolean mFullPssOrRssPending = false; /** - * If true, we are running under a test environment so will sample PSS from processes - * much more rapidly to try to collect better data when the tests are rapidly - * running through apps. + * If true, we are running under a test environment so will sample PSS or RSS from processes + * much more rapidly to try to collect better data when the tests are rapidly running through + * apps. */ - private volatile boolean mTestPssMode = false; + private volatile boolean mTestPssOrRssMode = false; private final LowMemDetector mLowMemDetector; @@ -598,7 +602,11 @@ public class AppProfiler { public void handleMessage(Message msg) { switch (msg.what) { case COLLECT_PSS_BG_MSG: - collectPssInBackground(); + if (!Flags.removeAppProfilerPssCollection()) { + collectPssInBackground(); + } else { + collectRssInBackground(); + } break; case DEFER_PSS_MSG: deferPssForActivityStart(); @@ -619,8 +627,8 @@ public class AppProfiler { long start = SystemClock.uptimeMillis(); MemInfoReader memInfo = null; synchronized (mProfilerLock) { - if (mFullPssPending) { - mFullPssPending = false; + if (mFullPssOrRssPending) { + mFullPssOrRssPending = false; memInfo = new MemInfoReader(); } } @@ -673,16 +681,16 @@ public class AppProfiler { int pid = -1; long lastPssTime; synchronized (mProfilerLock) { - if (mPendingPssProfiles.size() <= 0) { - if (mTestPssMode || DEBUG_PSS) { + if (mPendingPssOrRssProfiles.size() <= 0) { + if (mTestPssOrRssMode || DEBUG_PSS) { Slog.d(TAG_PSS, "Collected pss of " + num + " processes in " + (SystemClock.uptimeMillis() - start) + "ms"); } - mPendingPssProfiles.clear(); + mPendingPssOrRssProfiles.clear(); return; } - profile = mPendingPssProfiles.remove(0); + profile = mPendingPssOrRssProfiles.remove(0); procState = profile.getPssProcState(); statType = profile.getPssStatType(); lastPssTime = profile.getLastPssTime(); @@ -740,13 +748,149 @@ public class AppProfiler { } while (true); } + // This method is analogous to collectPssInBackground() and is intended to be used as a + // replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in + // methods outside of AppProfiler have generally been kept where a new RSS equivalent is not + // technically necessary. These can be updated once the flag is completely rolled out. + private void collectRssInBackground() { + long start = SystemClock.uptimeMillis(); + MemInfoReader memInfo = null; + synchronized (mProfilerLock) { + if (mFullPssOrRssPending) { + mFullPssOrRssPending = false; + memInfo = new MemInfoReader(); + } + } + if (memInfo != null) { + updateCpuStatsNow(); + long nativeTotalRss = 0; + final List<ProcessCpuTracker.Stats> stats; + synchronized (mProcessCpuTracker) { + stats = mProcessCpuTracker.getStats(st -> { + return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID; + }); + } + + // We assume that if PSS collection isn't needed or desired, RSS collection can be + // disabled as well. + if (!mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED) { + final int numOfStats = stats.size(); + for (int j = 0; j < numOfStats; j++) { + synchronized (mService.mPidsSelfLocked) { + if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) { + // This is one of our own processes; skip it. + continue; + } + } + nativeTotalRss += Debug.getRss(stats.get(j).pid, null); + } + } + + memInfo.readMemInfo(); + synchronized (mService.mProcessStats.mLock) { + // We assume that an enabled DEBUG_PSS can apply to RSS as well, since only one of + // either collectPssInBackground() or collectRssInBackground() will be used. + if (DEBUG_RSS) { + Slog.d(TAG_RSS, "Collected native and kernel memory in " + + (SystemClock.uptimeMillis() - start) + "ms"); + } + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + // The last value needs to be updated in log tags to refer to RSS; this will be + // updated once the flag is fully rolled out. + EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, + kernelKb * 1024, nativeTotalRss * 1024); + mService.mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeTotalRss); + } + } + + // This loop differs from its original form in collectPssInBackground(), as it does not + // collect USS or SwapPss (since those are reported in smaps, not status). + int num = 0; + do { + ProcessProfileRecord profile; + int procState; + int statType; + int pid = -1; + long lastRssTime; + synchronized (mProfilerLock) { + if (mPendingPssOrRssProfiles.size() <= 0) { + if (mTestPssOrRssMode || DEBUG_RSS) { + Slog.d(TAG_RSS, + "Collected rss of " + num + " processes in " + + (SystemClock.uptimeMillis() - start) + "ms"); + } + mPendingPssOrRssProfiles.clear(); + return; + } + profile = mPendingPssOrRssProfiles.remove(0); + procState = profile.getPssProcState(); + statType = profile.getPssStatType(); + lastRssTime = profile.getLastPssTime(); + long now = SystemClock.uptimeMillis(); + if (profile.getThread() != null && procState == profile.getSetProcState() + && (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) { + pid = profile.getPid(); + } else { + profile.abortNextPssTime(); + if (DEBUG_RSS) { + Slog.d(TAG_RSS, "Skipped rss collection of " + pid + + ": still need " + + (lastRssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now) + + "ms until safe"); + } + profile = null; + pid = 0; + } + } + if (profile != null) { + long startTime = SystemClock.currentThreadTimeMillis(); + // skip background RSS calculation under the following situations: + // - app is capturing camera imagery + // - app is frozen and we have already collected RSS once. + final boolean skipRSSCollection = + (profile.mApp.mOptRecord != null + && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen()) + || mService.isCameraActiveForUid(profile.mApp.uid) + || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED; + long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null); + long endTime = SystemClock.currentThreadTimeMillis(); + synchronized (mProfilerLock) { + if (rss != 0 && profile.getThread() != null + && profile.getSetProcState() == procState + && profile.getPid() == pid && profile.getLastPssTime() == lastRssTime) { + num++; + profile.commitNextPssTime(); + recordRssSampleLPf(profile, procState, rss, statType, endTime - startTime, + SystemClock.uptimeMillis()); + } else { + profile.abortNextPssTime(); + if (DEBUG_RSS) { + Slog.d(TAG_RSS, "Skipped rss collection of " + pid + + ": " + (profile.getThread() == null ? "NO_THREAD " : "") + + (skipRSSCollection ? "SKIP_RSS_COLLECTION " : "") + + (profile.getPid() != pid ? "PID_CHANGED " : "") + + " initState=" + procState + " curState=" + + profile.getSetProcState() + " " + + (profile.getLastPssTime() != lastRssTime + ? "TIME_CHANGED" : "")); + } + } + } + } + } while (true); + } + @GuardedBy("mProfilerLock") void updateNextPssTimeLPf(int procState, ProcessProfileRecord profile, long now, boolean forceUpdate) { if (!forceUpdate) { if (now <= profile.getNextPssTime() && now <= Math.max(profile.getLastPssTime() + ProcessList.PSS_MAX_INTERVAL, profile.getLastStateTime() - + ProcessList.minTimeFromStateChange(mTestPssMode))) { + + ProcessList.minTimeFromStateChange(mTestPssOrRssMode))) { // update is not due, ignore it. return; } @@ -755,7 +899,7 @@ public class AppProfiler { } } profile.setNextPssTime(profile.computeNextPssTime(procState, - mTestPssMode, mService.mAtmInternal.isSleeping(), now)); + mTestPssOrRssMode, mService.mAtmInternal.isSleeping(), now)); } /** @@ -776,8 +920,8 @@ public class AppProfiler { + " lastPss=" + profile.getLastPss() + " state=" + ProcessList.makeProcStateString(procState)); } - if (profile.getInitialIdlePss() == 0) { - profile.setInitialIdlePss(pss); + if (profile.getInitialIdlePssOrRss() == 0) { + profile.setInitialIdlePssOrRss(pss); } profile.setLastPss(pss); profile.setLastSwapPss(swapPss); @@ -813,6 +957,72 @@ public class AppProfiler { } } + /** + * Record new RSS sample for a process. + * + * This method is analogous to recordPssSampleLPf() and is intended to be used as a replacement + * if Flags.removeAppProfilerPssCollection() is enabled. Functionally, this differs in that PSS, + * SwapPss, and USS are no longer collected and reported. + * + * This method will also poll PSS if the app has requested that a heap dump be taken if its PSS + * reaches some threshold set with ActivityManager.setWatchHeapLimit(). + */ + @GuardedBy("mProfilerLock") + private void recordRssSampleLPf(ProcessProfileRecord profile, int procState, long rss, + int statType, long rssDuration, long now) { + final ProcessRecord proc = profile.mApp; + // TODO(b/296454553): writeAmPss needs to be renamed to writeAmRss, and the zeroed out + // fields need to be removed. This will be updated once the flag is fully rolled out to + // avoid churn in the .logtags file, which has a mapping of IDs to tags (and is also + // technically deprecated). + EventLogTags.writeAmPss( + profile.getPid(), proc.uid, proc.processName, /* pss = */ 0, /* uss = */ 0, + /* swapPss = */ 0, rss * 1024, statType, procState, rssDuration); + profile.setLastPssTime(now); + // The PSS here is emitted in logs, so we can zero it out instead of subbing in RSS. + profile.addPss(/* pss = */ 0, /* uss = */ 0, rss, true, statType, rssDuration); + if (DEBUG_RSS) { + Slog.d(TAG_RSS, + "rss of " + proc.toShortString() + ": " + rss + + " lastRss=" + profile.getLastRss() + + " state=" + ProcessList.makeProcStateString(procState)); + } + if (profile.getInitialIdlePssOrRss() == 0) { + profile.setInitialIdlePssOrRss(rss); + } + profile.setLastRss(rss); + if (procState >= ActivityManager.PROCESS_STATE_HOME) { + profile.setLastCachedRss(rss); + } + + final SparseArray<Pair<Long, String>> watchUids = + mMemWatchProcesses.getMap().get(proc.processName); + Long check = null; + if (watchUids != null) { + Pair<Long, String> val = watchUids.get(proc.uid); + if (val == null) { + val = watchUids.get(0); + } + if (val != null) { + check = val.first; + } + } + + if (check != null) { + long pss = Debug.getPss(profile.getPid(), null, null); + if ((pss * 1024) >= check && profile.getThread() != null + && mMemWatchDumpProcName == null) { + if (Build.IS_DEBUGGABLE || proc.isDebuggable()) { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); + startHeapDumpLPf(profile, false); + } else { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + + ", but debugging not enabled"); + } + } + } + } + private final class RecordPssRunnable implements Runnable { private final ProcessProfileRecord mProfile; private final Uri mDumpUri; @@ -984,10 +1194,10 @@ public class AppProfiler { */ @GuardedBy("mProfilerLock") private boolean requestPssLPf(ProcessProfileRecord profile, int procState) { - if (mPendingPssProfiles.contains(profile)) { + if (mPendingPssOrRssProfiles.contains(profile)) { return false; } - if (mPendingPssProfiles.size() == 0) { + if (mPendingPssOrRssProfiles.size() == 0) { final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0) ? mPssDeferralTime : 0; if (DEBUG_PSS && deferral > 0) { @@ -999,7 +1209,7 @@ public class AppProfiler { if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + profile.mApp); profile.setPssProcState(procState); profile.setPssStatType(ProcessStats.ADD_PSS_INTERNAL_SINGLE); - mPendingPssProfiles.add(profile); + mPendingPssOrRssProfiles.add(profile); return true; } @@ -1009,7 +1219,7 @@ public class AppProfiler { */ @GuardedBy("mProfilerLock") private void deferPssIfNeededLPf() { - if (mPendingPssProfiles.size() > 0) { + if (mPendingPssOrRssProfiles.size() > 0) { mBgHandler.removeMessages(BgHandler.COLLECT_PSS_BG_MSG); mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, mPssDeferralTime); } @@ -1063,12 +1273,12 @@ public class AppProfiler { Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); } mLastFullPssTime = now; - mFullPssPending = true; - for (int i = mPendingPssProfiles.size() - 1; i >= 0; i--) { - mPendingPssProfiles.get(i).abortNextPssTime(); + mFullPssOrRssPending = true; + for (int i = mPendingPssOrRssProfiles.size() - 1; i >= 0; i--) { + mPendingPssOrRssProfiles.get(i).abortNextPssTime(); } - mPendingPssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP()); - mPendingPssProfiles.clear(); + mPendingPssOrRssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP()); + mPendingPssOrRssProfiles.clear(); mService.mProcessList.forEachLruProcessesLOSP(false, app -> { final ProcessProfileRecord profile = app.mProfile; if (profile.getThread() == null @@ -1083,7 +1293,7 @@ public class AppProfiler { profile.setPssStatType(always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM); updateNextPssTimeLPf(profile.getSetProcState(), profile, now, true); - mPendingPssProfiles.add(profile); + mPendingPssOrRssProfiles.add(profile); } }); if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) { @@ -1094,7 +1304,7 @@ public class AppProfiler { void setTestPssMode(boolean enabled) { synchronized (mProcLock) { - mTestPssMode = enabled; + mTestPssOrRssMode = enabled; if (enabled) { // Whenever we enable the mode, we want to take a snapshot all of current // process mem use. @@ -1104,7 +1314,7 @@ public class AppProfiler { } boolean getTestPssMode() { - return mTestPssMode; + return mTestPssOrRssMode; } @GuardedBy("mService") @@ -2346,7 +2556,7 @@ public class AppProfiler { synchronized (mProfilerLock) { final ProcessProfileRecord profile = app.mProfile; mProcessesToGc.remove(app); - mPendingPssProfiles.remove(profile); + mPendingPssOrRssProfiles.remove(profile); profile.abortNextPssTime(); } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index b30334632b55..b00dcd6ccf1f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -144,6 +144,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.net.NetworkPolicyManager; +import android.os.Flags; import android.os.Handler; import android.os.IBinder; import android.os.PowerManagerInternal; @@ -2418,9 +2419,23 @@ public class OomAdjuster { // normally be a B service, but if we are low on RAM and it // is large we want to force it down since we would prefer to // keep launcher over it. + long lastPssOrRss = !Flags.removeAppProfilerPssCollection() + ? app.mProfile.getLastPss() : app.mProfile.getLastRss(); + + // RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how + // many shared pages a process uses. The threshold is increased if the flag for + // reading RSS instead of PSS is enabled. + // + // TODO(b/296454553): Tune the second value so that the relative number of + // service B is similar before/after this flag is enabled. + double thresholdModifier = !Flags.removeAppProfilerPssCollection() + ? 1 + : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER; + double cachedRestoreThreshold = + mProcessList.getCachedRestoreThresholdKb() * thresholdModifier; + if (!mService.mAppProfiler.isLastMemoryLevelNormal() - && app.mProfile.getLastPss() - >= mProcessList.getCachedRestoreThresholdKb()) { + && lastPssOrRss >= cachedRestoreThreshold) { state.setServiceHighRam(true); state.setServiceB(true); //Slog.i(TAG, "ADJ " + app + " high ram!"); diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index b852ef56fceb..d372108e0a47 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -67,10 +67,8 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; import android.content.pm.ServiceInfo; -import android.os.IBinder; import android.os.SystemClock; import android.os.Trace; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -80,7 +78,6 @@ import com.android.server.ServiceThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.function.Consumer; @@ -504,6 +501,28 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } + /** + * A helper consumer for collecting processes that have not been reached yet. To avoid object + * allocations every OomAdjuster update, the results will be stored in + * {@link UnreachedProcessCollector#processList}. The process list reader is responsible + * for setting it before usage, as well as, clearing the reachable state of each process in the + * list. + */ + private static class UnreachedProcessCollector implements Consumer<ProcessRecord> { + public ArrayList<ProcessRecord> processList = null; + @Override + public void accept(ProcessRecord process) { + if (process.mState.isReachable()) { + return; + } + process.mState.setReachable(true); + processList.add(process); + } + } + + private final UnreachedProcessCollector mUnreachedProcessCollector = + new UnreachedProcessCollector(); + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) { this(service, processList, activeUids, createAdjusterThread()); @@ -755,23 +774,8 @@ public class OomAdjusterModernImpl extends OomAdjuster { // We'll need to collect the upstream processes of the target apps here, because those // processes would potentially impact the procstate/adj via bindings. if (!fullUpdate) { - final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses, - clientProcesses); - - // If any of its upstream processes are in a cycle, - // move them into the candidate targets. - if (containsCycle) { - // Add all client apps to the target process list. - for (int i = 0, size = clientProcesses.size(); i < size; i++) { - final ProcessRecord client = clientProcesses.get(i); - final UidRecord uidRec = client.getUidRecord(); - targetProcesses.add(client); - if (uidRec != null) { - uids.put(uidRec.getUid(), uidRec); - } - } - clientProcesses.clear(); - } + collectExcludedClientProcessesLocked(targetProcesses, clientProcesses); + for (int i = 0, size = targetProcesses.size(); i < size; i++) { final ProcessRecord app = targetProcesses.valueAt(i); app.mState.resetCachedInfo(); @@ -807,102 +811,36 @@ public class OomAdjusterModernImpl extends OomAdjuster { } /** - * Collect the reversed reachable processes from the given {@code apps}, the result will be - * returned in the given {@code processes}, which will <em>NOT</em> include the processes from - * the given {@code apps}. + * Collect the client processes from the given {@code apps}, the result will be returned in the + * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given + * {@code apps}. */ @GuardedBy("mService") - private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps, + private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps, ArrayList<ProcessRecord> clientProcesses) { - final ArrayDeque<ProcessRecord> queue = mTmpQueue; - queue.clear(); - clientProcesses.clear(); - for (int i = 0, size = apps.size(); i < size; i++) { + // Mark all of the provided apps as reachable to avoid including them in the client list. + final int appsSize = apps.size(); + for (int i = 0; i < appsSize; i++) { final ProcessRecord app = apps.valueAt(i); app.mState.setReachable(true); - app.mState.setReversedReachable(true); - queue.offer(app); } - // Track if any of them reachables could include a cycle - boolean containsCycle = false; - - // Scan upstreams of the process record - for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) { - if (!pr.mState.isReachable()) { - // If not in the given initial set of apps, add it. - clientProcesses.add(pr); - } - final ProcessServiceRecord psr = pr.mServices; - for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { - final ServiceRecord s = psr.getRunningServiceAt(i); - final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = - s.getConnections(); - for (int j = serviceConnections.size() - 1; j >= 0; j--) { - final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); - for (int k = clist.size() - 1; k >= 0; k--) { - final ConnectionRecord cr = clist.get(k); - final ProcessRecord client = cr.binding.client; - containsCycle |= client.mState.isReversedReachable(); - if (client.mState.isReversedReachable()) { - continue; - } - queue.offer(client); - client.mState.setReversedReachable(true); - } - } - } - final ProcessProviderRecord ppr = pr.mProviders; - for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) { - final ContentProviderRecord cpr = ppr.getProviderAt(i); - for (int j = cpr.connections.size() - 1; j >= 0; j--) { - final ContentProviderConnection conn = cpr.connections.get(j); - final ProcessRecord client = conn.client; - containsCycle |= client.mState.isReversedReachable(); - if (client.mState.isReversedReachable()) { - continue; - } - queue.offer(client); - client.mState.setReversedReachable(true); - } - } - // If this process is a sandbox itself, also add the app on whose behalf - // its running - if (pr.isSdkSandbox) { - for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) { - ServiceRecord s = psr.getRunningServiceAt(is); - ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = - s.getConnections(); - for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { - ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); - for (int i = clist.size() - 1; i >= 0; i--) { - ConnectionRecord cr = clist.get(i); - ProcessRecord attributedApp = cr.binding.attributedClient; - if (attributedApp == null || attributedApp == pr) { - continue; - } - containsCycle |= attributedApp.mState.isReversedReachable(); - if (attributedApp.mState.isReversedReachable()) { - continue; - } - queue.offer(attributedApp); - attributedApp.mState.setReversedReachable(true); - } - } - } - } + clientProcesses.clear(); + mUnreachedProcessCollector.processList = clientProcesses; + for (int i = 0; i < appsSize; i++) { + final ProcessRecord app = apps.valueAt(i); + app.forEachClient(mUnreachedProcessCollector); } + mUnreachedProcessCollector.processList = null; // Reset the temporary bits. for (int i = clientProcesses.size() - 1; i >= 0; i--) { - clientProcesses.get(i).mState.setReversedReachable(false); + clientProcesses.get(i).mState.setReachable(false); } for (int i = 0, size = apps.size(); i < size; i++) { final ProcessRecord app = apps.valueAt(i); app.mState.setReachable(false); - app.mState.setReversedReachable(false); } - return containsCycle; } @GuardedBy({"mService", "mProcLock"}) @@ -917,10 +855,6 @@ public class OomAdjusterModernImpl extends OomAdjuster { final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; final int adjTarget = mProcessRecordAdjNodes.size() - 1; - final int appUid = !fullUpdate && targetProcesses.size() > 0 - ? targetProcesses.valueAt(0).uid : -1; - final int logUid = mService.mCurOomAdjUid; - mAdjSeq++; // All apps to be updated will be moved to the lowest slot. if (fullUpdate) { @@ -974,7 +908,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { // We don't update the adj list since we're resetting it below. } - // Now nodes are set into their slots, without facting in the bindings. + // Now nodes are set into their slots, without factoring in the bindings. // The nodes between the `lastNode` pointer and the TAIL should be the new nodes. // // The whole rationale here is that, the bindings from client to host app, won't elevate diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 2efac12a53a2..cb2b5fbe7a5a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -94,6 +94,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.DropBoxManager; +import android.os.Flags; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -4594,6 +4595,8 @@ public final class ProcessList { r.mProfile.getLastPss() * 1024, new StringBuilder())); proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString( r.mProfile.getLastSwapPss() * 1024, new StringBuilder())); + // TODO(b/296454553): This proto field should be replaced with last cached RSS once + // AppProfiler is no longer collecting PSS. proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString( r.mProfile.getLastCachedPss() * 1024, new StringBuilder())); proto.write(ProcessOomProto.Detail.CACHED, state.isCached()); @@ -4725,12 +4728,20 @@ public final class ProcessList { pw.print(" "); pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState())); pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState())); - pw.print(" lastPss="); - DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024); - pw.print(" lastSwapPss="); - DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024); - pw.print(" lastCachedPss="); - DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024); + // These values won't be collected if the flag is enabled. + if (!Flags.removeAppProfilerPssCollection()) { + pw.print(" lastPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024); + pw.print(" lastSwapPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024); + pw.print(" lastCachedPss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024); + } else { + pw.print(" lastRss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastRss() * 1024); + pw.print(" lastCachedRss="); + DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedRss() * 1024); + } pw.println(); pw.print(prefix); pw.print(" "); diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index 354f3d3d13e0..8ca64f83ea79 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -23,6 +23,7 @@ import android.app.IApplicationThread; import android.app.ProcessMemoryState.HostingComponentType; import android.content.pm.ApplicationInfo; import android.os.Debug; +import android.os.Flags; import android.os.Process; import android.os.SystemClock; import android.util.DebugUtils; @@ -32,7 +33,6 @@ import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.am.ProcessList.ProcStateMemTracker; import com.android.server.power.stats.BatteryStatsImpl; @@ -42,6 +42,8 @@ import java.util.concurrent.atomic.AtomicLong; /** * Profiling info of the process, such as PSS, cpu, etc. + * + * TODO(b/297542292): Update PSS names with RSS once AppProfiler's PSS profiling has been replaced. */ final class ProcessProfileRecord { final ProcessRecord mApp; @@ -76,7 +78,7 @@ final class ProcessProfileRecord { * Initial memory pss of process for idle maintenance. */ @GuardedBy("mProfilerLock") - private long mInitialIdlePss; + private long mInitialIdlePssOrRss; /** * Last computed memory pss. @@ -109,6 +111,14 @@ final class ProcessProfileRecord { private long mLastRss; /** + * Last computed rss when in cached state. + * + * This value is not set or retrieved unless Flags.removeAppProfilerPssCollection() is true. + */ + @GuardedBy("mProfilerLock") + private long mLastCachedRss; + + /** * Cache of last retrieve memory info, to throttle how frequently apps can request it. */ @GuardedBy("mProfilerLock") @@ -347,13 +357,13 @@ final class ProcessProfileRecord { } @GuardedBy("mProfilerLock") - long getInitialIdlePss() { - return mInitialIdlePss; + long getInitialIdlePssOrRss() { + return mInitialIdlePssOrRss; } @GuardedBy("mProfilerLock") - void setInitialIdlePss(long initialIdlePss) { - mInitialIdlePss = initialIdlePss; + void setInitialIdlePssOrRss(long initialIdlePssOrRss) { + mInitialIdlePssOrRss = initialIdlePssOrRss; } @GuardedBy("mProfilerLock") @@ -377,6 +387,16 @@ final class ProcessProfileRecord { } @GuardedBy("mProfilerLock") + long getLastCachedRss() { + return mLastCachedRss; + } + + @GuardedBy("mProfilerLock") + void setLastCachedRss(long lastCachedRss) { + mLastCachedRss = lastCachedRss; + } + + @GuardedBy("mProfilerLock") long getLastSwapPss() { return mLastSwapPss; } @@ -530,26 +550,6 @@ final class ProcessProfileRecord { } } - void reportCachedKill() { - synchronized (mService.mProcessStats.mLock) { - final ProcessState tracker = mBaseProcessTracker; - if (tracker != null) { - final PackageList pkgList = mApp.getPkgList(); - synchronized (pkgList) { - tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss); - pkgList.forEachPackageProcessStats(holder -> - FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED, - getUidForAttribution(mApp), - holder.state.getName(), - holder.state.getPackage(), - mLastCachedPss, - holder.appVersion) - ); - } - } - } - } - void setProcessTrackerState(int procState, int memFactor) { synchronized (mService.mProcessStats.mLock) { final ProcessState tracker = mBaseProcessTracker; @@ -676,27 +676,46 @@ final class ProcessProfileRecord { @GuardedBy("mService") void dumpPss(PrintWriter pw, String prefix, long nowUptime) { synchronized (mProfilerLock) { - pw.print(prefix); - pw.print("lastPssTime="); - TimeUtils.formatDuration(mLastPssTime, nowUptime, pw); - pw.print(" pssProcState="); - pw.print(mPssProcState); - pw.print(" pssStatType="); - pw.print(mPssStatType); - pw.print(" nextPssTime="); - TimeUtils.formatDuration(mNextPssTime, nowUptime, pw); - pw.println(); - pw.print(prefix); - pw.print("lastPss="); - DebugUtils.printSizeValue(pw, mLastPss * 1024); - pw.print(" lastSwapPss="); - DebugUtils.printSizeValue(pw, mLastSwapPss * 1024); - pw.print(" lastCachedPss="); - DebugUtils.printSizeValue(pw, mLastCachedPss * 1024); - pw.print(" lastCachedSwapPss="); - DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024); - pw.print(" lastRss="); - DebugUtils.printSizeValue(pw, mLastRss * 1024); + // TODO(b/297542292): Remove this case once PSS profiling is replaced + if (!Flags.removeAppProfilerPssCollection()) { + pw.print(prefix); + pw.print("lastPssTime="); + TimeUtils.formatDuration(mLastPssTime, nowUptime, pw); + pw.print(" pssProcState="); + pw.print(mPssProcState); + pw.print(" pssStatType="); + pw.print(mPssStatType); + pw.print(" nextPssTime="); + TimeUtils.formatDuration(mNextPssTime, nowUptime, pw); + pw.println(); + pw.print(prefix); + pw.print("lastPss="); + DebugUtils.printSizeValue(pw, mLastPss * 1024); + pw.print(" lastSwapPss="); + DebugUtils.printSizeValue(pw, mLastSwapPss * 1024); + pw.print(" lastCachedPss="); + DebugUtils.printSizeValue(pw, mLastCachedPss * 1024); + pw.print(" lastCachedSwapPss="); + DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024); + pw.print(" lastRss="); + DebugUtils.printSizeValue(pw, mLastRss * 1024); + } else { + pw.print(prefix); + pw.print("lastRssTime="); + TimeUtils.formatDuration(mLastPssTime, nowUptime, pw); + pw.print(" rssProcState="); + pw.print(mPssProcState); + pw.print(" rssStatType="); + pw.print(mPssStatType); + pw.print(" nextRssTime="); + TimeUtils.formatDuration(mNextPssTime, nowUptime, pw); + pw.println(); + pw.print(prefix); + pw.print("lastRss="); + DebugUtils.printSizeValue(pw, mLastRss * 1024); + pw.print(" lastCachedRss="); + DebugUtils.printSizeValue(pw, mLastCachedRss * 1024); + } pw.println(); pw.print(prefix); pw.print("trimMemoryLevel="); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 2c6e598ef0a4..b2082d9e8dc0 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -69,8 +69,10 @@ import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; /** * Full information about a particular process that @@ -1613,4 +1615,50 @@ class ProcessRecord implements WindowProcessListener { public boolean wasForceStopped() { return mWasForceStopped; } + + /** + * Traverses all client processes and feed them to consumer. + */ + @GuardedBy("mProcLock") + void forEachClient(@NonNull Consumer<ProcessRecord> consumer) { + for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) { + final ServiceRecord s = mServices.getRunningServiceAt(i); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int j = serviceConnections.size() - 1; j >= 0; j--) { + final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); + for (int k = clist.size() - 1; k >= 0; k--) { + final ConnectionRecord cr = clist.get(k); + consumer.accept(cr.binding.client); + } + } + } + for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) { + final ContentProviderRecord cpr = mProviders.getProviderAt(i); + for (int j = cpr.connections.size() - 1; j >= 0; j--) { + final ContentProviderConnection conn = cpr.connections.get(j); + consumer.accept(conn.client); + } + } + // If this process is a sandbox itself, also add the app on whose behalf + // its running + if (isSdkSandbox) { + for (int is = mServices.numberOfRunningServices() - 1; is >= 0; is--) { + ServiceRecord s = mServices.getRunningServiceAt(is); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); + for (int i = clist.size() - 1; i >= 0; i--) { + ConnectionRecord cr = clist.get(i); + ProcessRecord attributedApp = cr.binding.attributedClient; + if (attributedApp == null || attributedApp == this) { + continue; + } + consumer.accept(attributedApp); + } + } + } + } + } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 27c08763fab0..5ad921fd0bae 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -29,6 +29,7 @@ import static com.android.server.am.ProcessRecord.TAG; import android.annotation.ElapsedRealtimeLong; import android.app.ActivityManager; import android.content.ComponentName; +import android.os.Flags; import android.os.SystemClock; import android.os.Trace; import android.util.Slog; @@ -378,12 +379,6 @@ final class ProcessStateRecord { private boolean mReachable; /** - * Whether or not this process is reversed reachable from given process. - */ - @GuardedBy("mService") - private boolean mReversedReachable; - - /** * The most recent time when the last visible activity within this process became invisible. * * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is @@ -996,16 +991,6 @@ final class ProcessStateRecord { } @GuardedBy("mService") - boolean isReversedReachable() { - return mReversedReachable; - } - - @GuardedBy("mService") - void setReversedReachable(boolean reversedReachable) { - mReversedReachable = reversedReachable; - } - - @GuardedBy("mService") void resetCachedInfo() { mCachedHasActivities = VALUE_INVALID; mCachedIsHeavyWeight = VALUE_INVALID; @@ -1366,7 +1351,12 @@ final class ProcessStateRecord { } if (mNotCachedSinceIdle) { pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle); - pw.print(" initialIdlePss="); pw.println(mApp.mProfile.getInitialIdlePss()); + if (!Flags.removeAppProfilerPssCollection()) { + pw.print(" initialIdlePss="); + } else { + pw.print(" initialIdleRss="); + } + pw.println(mApp.mProfile.getInitialIdlePssOrRss()); } if (hasTopUi() || hasOverlayUi() || mRunningRemoteAnimation) { pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi()); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 028be88fd3ac..192fd6f1a9ce 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -123,6 +123,7 @@ public class SettingsToPropertiesMapper { "angle", "app_widgets", "arc_next", + "avic", "bluetooth", "build", "biometrics", @@ -140,6 +141,7 @@ public class SettingsToPropertiesMapper { "context_hub", "core_experiments_team_internal", "core_graphics", + "dck_framework", "game", "haptics", "hardware_backed_security_mainline", @@ -184,6 +186,7 @@ public class SettingsToPropertiesMapper { "wear_security", "wear_system_health", "wear_systems", + "wear_sysui", "window_surfaces", "windowing_frontend", }; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 613416c0bbad..14aab13bf638 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -223,12 +223,6 @@ public class AppOpsService extends IAppOpsService.Stub { // Constant meaning that any UID should be matched when dispatching callbacks private static final int UID_ANY = -2; - private static final int[] ADB_NON_SETTABLE_APP_IDS = { - Process.ROOT_UID, - Process.SYSTEM_UID, - Process.SHELL_UID, - }; - private static final int[] OPS_RESTRICTED_ON_SUSPEND = { OP_PLAY_AUDIO, OP_RECORD_AUDIO, @@ -4941,32 +4935,17 @@ public class AppOpsService extends IAppOpsService.Stub { } if (!shell.targetsUid && shell.packageName != null) { - if (ArrayUtils.contains(ADB_NON_SETTABLE_APP_IDS, - UserHandle.getAppId(shell.packageUid))) { - err.println("Error: Cannot set app ops for uid " + shell.packageUid); - return -1; - } shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode); } else if (shell.targetsUid && shell.packageName != null) { try { final int uid = shell.mInternal.mContext.getPackageManager() .getPackageUidAsUser(shell.packageName, shell.userId); - if (ArrayUtils.contains(ADB_NON_SETTABLE_APP_IDS, - UserHandle.getAppId(uid))) { - err.println("Error: Cannot set app ops for uid " + uid); - return -1; - } shell.mInterface.setUidMode(shell.op, uid, mode); } catch (PackageManager.NameNotFoundException e) { return -1; } } else { - if (ArrayUtils.contains(ADB_NON_SETTABLE_APP_IDS, - UserHandle.getAppId(shell.nonpackageUid))) { - err.println("Error: Cannot set app ops for uid " + shell.nonpackageUid); - return -1; - } shell.mInterface.setUidMode(shell.op, shell.nonpackageUid, mode); } return 0; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1e38c0f25157..1ef4333ddbd8 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -118,6 +118,7 @@ import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.ICommunicationDeviceDispatcher; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.IDevicesForAttributesCallback; +import android.media.ILoudnessCodecUpdatesDispatcher; import android.media.IMuteAwaitConnectionCallback; import android.media.IPlaybackConfigDispatcher; import android.media.IPreferredMixerAttributesDispatcher; @@ -132,6 +133,7 @@ import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; +import android.media.LoudnessCodecFormat; import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; import android.media.PlayerBase; @@ -4267,7 +4269,8 @@ public class AudioService extends IAudioService.Stub if (device == null) { // call was already logged in setDeviceVolume() sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, - index/*val1*/, flags/*val2*/, callingPackage)); + index/*val1*/, flags/*val2*/, getStreamVolume(streamType) /*val3*/, + callingPackage)); } setStreamVolume(streamType, index, flags, device, callingPackage, callingPackage, attributionTag, @@ -4370,6 +4373,7 @@ public class AudioService extends IAudioService.Stub if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) { mSoundDoseHelper.scheduleMusicActiveCheck(); } + // Update playback active state for all apps in audio mode stack. // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message @@ -8781,7 +8785,7 @@ public class AudioService extends IAudioService.Stub mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, mStreamVolumeAlias[mStreamType]); AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent( - mStreamType, mStreamVolumeAlias[mStreamType], index)); + mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex)); sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions); } } @@ -10590,6 +10594,51 @@ public class AudioService extends IAudioService.Stub return anonymizeAudioDeviceAttributesUnchecked(ada); } + // ======================================================================================== + // LoudnessCodecConfigurator + + @Override + public void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) { + // TODO: implement + } + + @Override + public void unregisterLoudnessCodecUpdatesDispatcher( + ILoudnessCodecUpdatesDispatcher dispatcher) { + // TODO: implement + } + + @Override + public void startLoudnessCodecUpdates(int piid) { + // TODO: implement + } + + @Override + public void stopLoudnessCodecUpdates(int piid) { + // TODO: implement + } + + @Override + public void addLoudnesssCodecFormat(int piid, LoudnessCodecFormat format) { + // TODO: implement + } + + @Override + public void addLoudnesssCodecFormatList(int piid, List<LoudnessCodecFormat> format) { + // TODO: implement + } + + @Override + public void removeLoudnessCodecFormat(int piid, LoudnessCodecFormat format) { + // TODO: implement + } + + @Override + public PersistableBundle getLoudnessParams(int piid, LoudnessCodecFormat format) { + // TODO: implement + return null; + } + //========================================================================================== // camera sound is forced if any of the resources corresponding to one active SIM diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 21a7d31cf691..f69b9f6523cc 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -151,11 +151,13 @@ public class AudioServiceEvents { final int mStreamType; final int mAliasStreamType; final int mIndex; + final int mOldIndex; - VolChangedBroadcastEvent(int stream, int alias, int index) { + VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) { mStreamType = stream; mAliasStreamType = alias; mIndex = index; + mOldIndex = oldIndex; } @Override @@ -163,7 +165,8 @@ public class AudioServiceEvents { return new StringBuilder("sending VOLUME_CHANGED stream:") .append(AudioSystem.streamToString(mStreamType)) .append(" index:").append(mIndex) - .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType)) + .append(" (was:").append(mOldIndex) + .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType)) .toString(); } } @@ -234,19 +237,35 @@ public class AudioServiceEvents { final int mStream; final int mVal1; final int mVal2; + final int mVal3; final String mCaller; final String mGroupName; + /** used for VOL_SET_STREAM_VOL */ + VolumeEvent(int op, int stream, int val1, int val2, int val3, String caller) { + mOp = op; + mStream = stream; + mVal1 = val1; + mVal2 = val2; + mVal3 = val3; + mCaller = caller; + // unused + mGroupName = null; + logMetricEvent(); + } + /** used for VOL_ADJUST_VOL_UID, * VOL_ADJUST_SUGG_VOL, * VOL_ADJUST_STREAM_VOL, - * VOL_SET_STREAM_VOL */ + */ VolumeEvent(int op, int stream, int val1, int val2, String caller) { mOp = op; mStream = stream; mVal1 = val1; mVal2 = val2; mCaller = caller; + // unused + mVal3 = -1; mGroupName = null; logMetricEvent(); } @@ -257,6 +276,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = gainDb; // unused + mVal3 = -1; mStream = -1; mCaller = null; mGroupName = null; @@ -269,6 +289,7 @@ public class AudioServiceEvents { mVal1 = index; // unused mVal2 = 0; + mVal3 = -1; mStream = -1; mCaller = null; mGroupName = null; @@ -282,6 +303,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = voiceActive ? 1 : 0; // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -294,6 +316,7 @@ public class AudioServiceEvents { mVal1 = index; mVal2 = mode; // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -308,6 +331,8 @@ public class AudioServiceEvents { mVal2 = flags; mCaller = caller; mGroupName = group; + // unused + mVal3 = -1; logMetricEvent(); } @@ -317,8 +342,10 @@ public class AudioServiceEvents { mStream = stream; mVal1 = state ? 1 : 0; mVal2 = 0; + // unused mCaller = null; mGroupName = null; + mVal3 = -1; logMetricEvent(); } @@ -328,6 +355,8 @@ public class AudioServiceEvents { mStream = -1; mVal1 = state ? 1 : 0; mVal2 = 0; + // unused + mVal3 = -1; mCaller = null; mGroupName = null; logMetricEvent(); @@ -386,6 +415,7 @@ public class AudioServiceEvents { .set(MediaMetrics.Property.EVENT, "setStreamVolume") .set(MediaMetrics.Property.FLAGS, mVal2) .set(MediaMetrics.Property.INDEX, mVal1) + .set(MediaMetrics.Property.OLD_INDEX, mVal3) .set(MediaMetrics.Property.STREAM_TYPE, AudioSystem.streamToString(mStream)) .record(); @@ -478,6 +508,7 @@ public class AudioServiceEvents { .append(AudioSystem.streamToString(mStream)) .append(" index:").append(mVal1) .append(" flags:0x").append(Integer.toHexString(mVal2)) + .append(" oldIndex:").append(mVal3) .append(") from ").append(mCaller) .toString(); case VOL_SET_HEARING_AID_VOL: diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index b9ccbfbf55e7..c5073001a672 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -576,7 +576,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onDialogAnimatedIn(boolean startFingerprintNow) { - if (mState != STATE_AUTH_STARTED) { + if (mState != STATE_AUTH_STARTED && mState != STATE_ERROR_PENDING_SYSUI) { Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState); return; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b0abf94ba525..aef224843b2f 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -2549,15 +2549,14 @@ public class Vpn { * secondary thread to perform connection work, returning quickly. * * Should only be called to respond to Binder requests as this enforces caller permission. Use - * {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the + * {@link #startLegacyVpnPrivileged(VpnProfile)} to skip the * permission check only when the caller is trusted (or the call is initiated by the system). */ - public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying, - LinkProperties egress) { + public void startLegacyVpn(VpnProfile profile) { enforceControlPermission(); final long token = Binder.clearCallingIdentity(); try { - startLegacyVpnPrivileged(profile, underlying, egress); + startLegacyVpnPrivileged(profile); } finally { Binder.restoreCallingIdentity(token); } @@ -2616,13 +2615,12 @@ public class Vpn { } /** - * Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not - * check permissions under the assumption that the caller is the system. + * Like {@link #startLegacyVpn(VpnProfile)}, but does not check permissions under + * the assumption that the caller is the system. * * Callers are responsible for checking permissions if needed. */ - public void startLegacyVpnPrivileged(VpnProfile profileToStart, - @Nullable Network underlying, @NonNull LinkProperties egress) { + public void startLegacyVpnPrivileged(VpnProfile profileToStart) { final VpnProfile profile = profileToStart.clone(); UserInfo user = mUserManager.getUserInfo(mUserId); if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 669580189d7c..9fcaa1e2af16 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -38,6 +38,7 @@ public final class DisplayBrightnessState { private final boolean mShouldUseAutoBrightness; private final boolean mIsSlowChange; + private final boolean mShouldUpdateScreenBrightnessSetting; private final float mCustomAnimationRate; @@ -50,6 +51,7 @@ public final class DisplayBrightnessState { mIsSlowChange = builder.isSlowChange(); mMaxBrightness = builder.getMaxBrightness(); mCustomAnimationRate = builder.getCustomAnimationRate(); + mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting(); } /** @@ -109,6 +111,13 @@ public final class DisplayBrightnessState { return mCustomAnimationRate; } + /** + * @return {@code true} if the screen brightness setting should be updated + */ + public boolean shouldUpdateScreenBrightnessSetting() { + return mShouldUpdateScreenBrightnessSetting; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:"); @@ -123,6 +132,8 @@ public final class DisplayBrightnessState { stringBuilder.append("\n isSlowChange:").append(mIsSlowChange); stringBuilder.append("\n maxBrightness:").append(mMaxBrightness); stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate); + stringBuilder.append("\n shouldUpdateScreenBrightnessSetting:") + .append(mShouldUpdateScreenBrightnessSetting); return stringBuilder.toString(); } @@ -149,13 +160,16 @@ public final class DisplayBrightnessState { && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness() && mIsSlowChange == otherState.isSlowChange() && mMaxBrightness == otherState.getMaxBrightness() - && mCustomAnimationRate == otherState.getCustomAnimationRate(); + && mCustomAnimationRate == otherState.getCustomAnimationRate() + && mShouldUpdateScreenBrightnessSetting + == otherState.shouldUpdateScreenBrightnessSetting(); } @Override public int hashCode() { return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason, - mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate); + mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate, + mShouldUpdateScreenBrightnessSetting); } /** @@ -177,6 +191,7 @@ public final class DisplayBrightnessState { private boolean mIsSlowChange; private float mMaxBrightness; private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; + private boolean mShouldUpdateScreenBrightnessSetting; /** * Create a builder starting with the values from the specified {@link @@ -194,6 +209,8 @@ public final class DisplayBrightnessState { builder.setIsSlowChange(state.isSlowChange()); builder.setMaxBrightness(state.getMaxBrightness()); builder.setCustomAnimationRate(state.getCustomAnimationRate()); + builder.setShouldUpdateScreenBrightnessSetting( + state.shouldUpdateScreenBrightnessSetting()); return builder; } @@ -290,8 +307,8 @@ public final class DisplayBrightnessState { /** * See {@link DisplayBrightnessState#isSlowChange()}. */ - public Builder setIsSlowChange(boolean shouldUseAutoBrightness) { - this.mIsSlowChange = shouldUseAutoBrightness; + public Builder setIsSlowChange(boolean isSlowChange) { + this.mIsSlowChange = isSlowChange; return this; } @@ -334,6 +351,22 @@ public final class DisplayBrightnessState { } /** + * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}. + */ + public boolean shouldUpdateScreenBrightnessSetting() { + return mShouldUpdateScreenBrightnessSetting; + } + + /** + * See {@link DisplayBrightnessState#shouldUpdateScreenBrightnessSetting()}. + */ + public Builder setShouldUpdateScreenBrightnessSetting( + boolean shouldUpdateScreenBrightnessSetting) { + mShouldUpdateScreenBrightnessSetting = shouldUpdateScreenBrightnessSetting; + return this; + } + + /** * This is used to construct an immutable DisplayBrightnessState object from its builder */ public DisplayBrightnessState build() { diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 9f4f78794659..2fdf90d7d286 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -209,7 +209,7 @@ abstract class DisplayDevice { int state, float brightnessState, float sdrBrightnessState, - @Nullable DisplayOffloadSession displayOffloadSession) { + @Nullable DisplayOffloadSessionImpl displayOffloadSession) { return null; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 11f4e5f75b11..e99f82aee96d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4948,20 +4948,8 @@ public final class DisplayManagerService extends SystemService { return null; } - DisplayOffloadSession session = - new DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) { - synchronized (mSyncRoot) { - displayPowerController.overrideDozeScreenState(displayState); - } - } - - @Override - public DisplayOffloader getDisplayOffloader() { - return displayOffloader; - } - }; + DisplayOffloadSessionImpl session = new DisplayOffloadSessionImpl(displayOffloader, + displayPowerController); logicalDisplay.setDisplayOffloadSessionLocked(session); displayPowerController.setDisplayOffloadSession(session); return session; diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java new file mode 100644 index 000000000000..1bd556bdcc4f --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -0,0 +1,91 @@ +/* + * 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 com.android.server.display; + +import android.annotation.Nullable; +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; +import android.os.Trace; + +/** + * An implementation of the offload session that keeps track of whether the session is active. + * An offload session is used to control the display's brightness using the offload chip. + */ +public class DisplayOffloadSessionImpl implements DisplayManagerInternal.DisplayOffloadSession { + + @Nullable + private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + private final DisplayPowerControllerInterface mDisplayPowerController; + private boolean mIsActive; + + public DisplayOffloadSessionImpl( + @Nullable DisplayManagerInternal.DisplayOffloader displayOffloader, + DisplayPowerControllerInterface displayPowerController) { + mDisplayOffloader = displayOffloader; + mDisplayPowerController = displayPowerController; + } + + @Override + public void setDozeStateOverride(int displayState) { + mDisplayPowerController.overrideDozeScreenState(displayState); + } + + @Override + public boolean isActive() { + return mIsActive; + } + + @Override + public void updateBrightness(float brightness) { + if (mIsActive) { + mDisplayPowerController.setBrightnessFromOffload(brightness); + } + } + + /** + * Start the offload session. The method returns if the session is already active. + * @return Whether the session was started successfully + */ + public boolean startOffload() { + if (mDisplayOffloader == null || mIsActive) { + return false; + } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); + try { + return mIsActive = mDisplayOffloader.startOffload(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } + + /** + * Stop the offload session. The method returns if the session is not active. + */ + public void stopOffload() { + if (mDisplayOffloader == null || !mIsActive) { + return; + } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "DisplayOffloader#stopOffload"); + try { + mDisplayOffloader.stopOffload(); + mIsActive = false; + mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5761c31b29bf..f3d761a7372b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2224,6 +2224,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public void setBrightnessFromOffload(float brightness) { + // The old DPC is no longer supported + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index a6155da86f9a..d4e0cbb126ed 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -151,6 +151,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private static final int MSG_SET_DWBC_STRONG_MODE = 14; private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15; private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16; + private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17; @@ -562,7 +563,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal new DisplayBrightnessController(context, null, mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault, brightnessSetting, () -> postBrightnessChangeRunnable(), - new HandlerExecutor(mHandler)); + new HandlerExecutor(mHandler), flags); mBrightnessClamperController = mInjector.getBrightnessClamperController( mHandler, modeChangeCallback::run, @@ -1394,7 +1395,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); - boolean updateScreenBrightnessSetting = false; + boolean updateScreenBrightnessSetting = + displayBrightnessState.shouldUpdateScreenBrightnessSetting(); float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness(); // Apply auto-brightness. int brightnessAdjustmentFlags = 0; @@ -1854,6 +1856,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } @Override + public void setBrightnessFromOffload(float brightness) { + Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD, + Float.floatToIntBits(brightness), 0 /*unused*/); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); + } + + @Override public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( @@ -2886,6 +2895,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal case MSG_SET_DWBC_LOGGING_ENABLED: setDwbcLoggingEnabled(msg.arg1); break; + case MSG_SET_BRIGHTNESS_FROM_OFFLOAD: + mDisplayBrightnessController.setBrightnessFromOffload( + Float.intBitsToFloat(msg.arg1)); + updatePowerState(); + break; } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index 181386a93c71..72079a4c82fe 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -22,6 +22,7 @@ import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; import java.io.PrintWriter; @@ -150,6 +151,14 @@ public interface DisplayPowerControllerInterface { void setTemporaryAutoBrightnessAdjustment(float adjustment); /** + * Sets temporary brightness from the offload chip until we get a brightness value from + * the light sensor. + * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and + * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored. + */ + void setBrightnessFromOffload(float brightness); + + /** * Gets the screen brightness setting */ float getScreenBrightnessSetting(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index be3207dfb4ee..ff9a1ab61b13 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -19,11 +19,11 @@ package com.android.server.display; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.Mode.INVALID_MODE_ID; +import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; -import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -238,7 +238,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mAllmRequested; private boolean mGameContentTypeRequested; private boolean mSidekickActive; - private boolean mDisplayOffloadActive; private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo; // The supported display modes according to SurfaceFlinger private SurfaceControl.DisplayMode[] mSfDisplayModes; @@ -765,7 +764,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { final int state, final float brightnessState, final float sdrBrightnessState, - DisplayOffloadSession displayOffloadSession) { + @Nullable DisplayOffloadSessionImpl displayOffloadSession) { // Assume that the brightness is off if the display is being turned off. assert state != Display.STATE_OFF @@ -832,25 +831,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { + ", state=" + Display.stateToString(state) + ")"); } - DisplayOffloader displayOffloader = - displayOffloadSession == null - ? null - : displayOffloadSession.getDisplayOffloader(); - boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled(); // We must tell sidekick/displayoffload to stop controlling the display // before we can change its power mode, so do that first. if (isDisplayOffloadEnabled) { - if (mDisplayOffloadActive && displayOffloader != null) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, - "DisplayOffloader#stopOffload"); - try { - displayOffloader.stopOffload(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); - } - mDisplayOffloadActive = false; + if (displayOffloadSession != null) { + displayOffloadSession.stopOffload(); } } else { if (mSidekickActive) { @@ -881,16 +868,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { // have a sidekick/displayoffload available, tell it now that it can take // control. if (isDisplayOffloadEnabled) { - if (DisplayOffloadSession.isSupportedOffloadState(state) && - displayOffloader != null - && !mDisplayOffloadActive) { - Trace.traceBegin( - Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); - try { - mDisplayOffloadActive = displayOffloader.startOffload(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); - } + if (DisplayOffloadSession.isSupportedOffloadState(state) + && displayOffloadSession != null) { + displayOffloadSession.startOffload(); } } else { if (Display.isSuspendedState(state) && state != Display.STATE_OFF diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 3d4209e0d6f3..ba321ae5d807 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -138,7 +138,7 @@ final class LogicalDisplay { private final Rect mTempDisplayRect = new Rect(); /** A session token that controls the offloading operations of this logical display. */ - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private DisplayOffloadSessionImpl mDisplayOffloadSession; /** * Name of a display group to which the display is assigned. @@ -969,12 +969,11 @@ final class LogicalDisplay { return mDisplayGroupName; } - public void setDisplayOffloadSessionLocked( - DisplayManagerInternal.DisplayOffloadSession session) { + public void setDisplayOffloadSessionLocked(DisplayOffloadSessionImpl session) { mDisplayOffloadSession = session; } - public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() { + public DisplayOffloadSessionImpl getDisplayOffloadSessionLocked() { return mDisplayOffloadSession; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 90e32a685a34..edbd42465534 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -43,7 +43,6 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; -import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.media.projection.IMediaProjection; @@ -380,7 +379,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @Override public Runnable requestDisplayStateLocked(int state, float brightnessState, - float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) { + float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) { if (state != mDisplayState) { mDisplayState = state; if (state == Display.STATE_OFF) { diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java index d7ae2699ee2d..8fe5f213d766 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java @@ -39,7 +39,8 @@ public final class BrightnessReason { public static final int REASON_BOOST = 8; public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9; public static final int REASON_FOLLOWER = 10; - public static final int REASON_MAX = REASON_FOLLOWER; + public static final int REASON_OFFLOAD = 11; + public static final int REASON_MAX = REASON_OFFLOAD; public static final int MODIFIER_DIMMED = 0x1; public static final int MODIFIER_LOW_POWER = 0x2; @@ -196,6 +197,8 @@ public final class BrightnessReason { return "screen_off_brightness_sensor"; case REASON_FOLLOWER: return "follower"; + case REASON_OFFLOAD: + return "offload"; default: return Integer.toString(reason); } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index d6f0098c13cb..617befbbd17d 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -31,6 +31,7 @@ import com.android.server.display.BrightnessSetting; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -104,7 +105,8 @@ public final class DisplayBrightnessController { */ public DisplayBrightnessController(Context context, Injector injector, int displayId, float defaultScreenBrightness, BrightnessSetting brightnessSetting, - Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor) { + Runnable onBrightnessChangeRunnable, HandlerExecutor brightnessChangeExecutor, + DisplayManagerFlags flags) { if (injector == null) { injector = new Injector(); } @@ -116,7 +118,7 @@ public final class DisplayBrightnessController { mCurrentScreenBrightness = getScreenBrightnessSetting(); mOnBrightnessChangeRunnable = onBrightnessChangeRunnable; mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context, - displayId); + displayId, flags); mBrightnessChangeExecutor = brightnessChangeExecutor; mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean( com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay); @@ -172,6 +174,18 @@ public final class DisplayBrightnessController { } /** + * Sets the brightness from the offload session. + */ + public void setBrightnessFromOffload(float brightness) { + synchronized (mLock) { + if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) { + mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() + .setOffloadScreenBrightness(brightness); + } + } + } + + /** * Returns a boolean flag indicating if the light sensor is to be used to decide the screen * brightness when dozing */ @@ -423,8 +437,9 @@ public final class DisplayBrightnessController { @VisibleForTesting static class Injector { DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context, - int displayId) { - return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId); + int displayId, DisplayManagerFlags flags) { + return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId, + flags); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index f141c20158cd..055f94a23363 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -17,6 +17,7 @@ package com.android.server.display.brightness; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.display.DisplayManagerInternal; import android.util.IndentingPrintWriter; @@ -31,9 +32,11 @@ import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy; import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -63,6 +66,10 @@ public class DisplayBrightnessStrategySelector { private final InvalidBrightnessStrategy mInvalidBrightnessStrategy; // Controls brightness when automatic (adaptive) brightness is running. private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy; + // Controls the brightness if adaptive brightness is on and there exists an active offload + // session. Brightness value is provided by the offload session. + @Nullable + private final OffloadBrightnessStrategy mOffloadBrightnessStrategy; // We take note of the old brightness strategy so that we can know when the strategy changes. private String mOldBrightnessStrategyName; @@ -72,7 +79,8 @@ public class DisplayBrightnessStrategySelector { /** * The constructor of DozeBrightnessStrategy. */ - public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) { + public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId, + DisplayManagerFlags flags) { if (injector == null) { injector = new Injector(); } @@ -85,6 +93,11 @@ public class DisplayBrightnessStrategySelector { mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId); mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy(); mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId); + if (flags.isDisplayOffloadEnabled()) { + mOffloadBrightnessStrategy = injector.getOffloadBrightnessStrategy(); + } else { + mOffloadBrightnessStrategy = null; + } mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( R.bool.config_allowAutoBrightnessWhileDozing); mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); @@ -114,6 +127,9 @@ public class DisplayBrightnessStrategySelector { } else if (BrightnessUtils.isValidBrightnessValue( mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) { displayBrightnessStrategy = mTemporaryBrightnessStrategy; + } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue( + mOffloadBrightnessStrategy.getOffloadScreenBrightness())) { + displayBrightnessStrategy = mOffloadBrightnessStrategy; } if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) { @@ -138,6 +154,11 @@ public class DisplayBrightnessStrategySelector { return mAutomaticBrightnessStrategy; } + @Nullable + public OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return mOffloadBrightnessStrategy; + } + /** * Returns a boolean flag indicating if the light sensor is to be used to decide the screen * brightness when dozing @@ -159,6 +180,9 @@ public class DisplayBrightnessStrategySelector { + mAllowAutoBrightnessWhileDozingConfig); IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); mTemporaryBrightnessStrategy.dump(ipw); + if (mOffloadBrightnessStrategy != null) { + mOffloadBrightnessStrategy.dump(ipw); + } } /** @@ -210,5 +234,9 @@ public class DisplayBrightnessStrategySelector { AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) { return new AutomaticBrightnessStrategy(context, displayId); } + + OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return new OffloadBrightnessStrategy(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index bcd52598edd2..3c23b5c10671 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -107,6 +107,7 @@ public class AutomaticBrightnessStrategy { mIsAutoBrightnessEnabled = shouldUseAutoBrightness() && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightnessReason != BrightnessReason.REASON_OVERRIDE + && brightnessReason != BrightnessReason.REASON_OFFLOAD && mAutomaticBrightnessController != null; mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness() && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze); diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java new file mode 100644 index 000000000000..55f8914e26f6 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java @@ -0,0 +1,74 @@ +/* + * 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 com.android.server.display.brightness.strategy; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import java.io.PrintWriter; + +/** + * Manages the brightness of the display when auto-brightness is on, the screen has just turned on + * and there is no available lux reading yet. The brightness value is read from the offload chip. + */ +public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy { + + private float mOffloadScreenBrightness; + + public OffloadBrightnessStrategy() { + mOffloadScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + } + + @Override + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD); + return new DisplayBrightnessState.Builder() + .setBrightness(mOffloadScreenBrightness) + .setSdrBrightness(mOffloadScreenBrightness) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(getName()) + .setIsSlowChange(false) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + } + + @Override + public String getName() { + return "OffloadBrightnessStrategy"; + } + + public float getOffloadScreenBrightness() { + return mOffloadScreenBrightness; + } + + public void setOffloadScreenBrightness(float offloadScreenBrightness) { + mOffloadScreenBrightness = offloadScreenBrightness; + } + + /** + * Dumps the state of this class. + */ + public void dump(PrintWriter writer) { + writer.println("OffloadBrightnessStrategy:"); + writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness); + } +} diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index aab491ee1def..8e0289ef1b43 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -47,9 +47,6 @@ class InputSettingsObserver extends ContentObserver { private final NativeInputManagerService mNative; private final Map<Uri, Consumer<String /* reason*/>> mObservers; - // Cache prevent notifying same KeyRepeatInfo data to native code multiple times. - private KeyRepeatInfo mLastKeyRepeatInfoSettingsUpdate; - InputSettingsObserver(Context context, Handler handler, InputManagerService service, NativeInputManagerService nativeIms) { super(handler); @@ -84,9 +81,9 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES), (reason) -> updateShowKeyPresses()), Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_TIMEOUT_MS), - (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())), + (reason) -> updateKeyRepeatInfo()), Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS), - (reason) -> updateKeyRepeatInfo(getLatestLongPressTimeoutValue())), + (reason) -> updateKeyRepeatInfo()), Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT), (reason) -> updateShowRotaryInput())); } @@ -182,46 +179,32 @@ class InputSettingsObserver extends ContentObserver { } private void updateLongPressTimeout(String reason) { - final int longPressTimeoutValue = getLatestLongPressTimeoutValue(); - - // Before the key repeat timeout was introduced, some users relied on changing - // LONG_PRESS_TIMEOUT settings to also change the key repeat timeout. To support this - // backward compatibility, we'll preemptively update key repeat info here, in case where - // key repeat timeout was never set, and user is still relying on long press timeout value. - updateKeyRepeatInfo(longPressTimeoutValue); + // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value. + final int longPressTimeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT, + UserHandle.USER_CURRENT); final boolean featureEnabledFlag = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, DEEP_PRESS_ENABLED, true /* default */); final boolean enabled = featureEnabledFlag - && longPressTimeoutValue <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT; + && longPressTimeoutMs <= ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT; Log.i(TAG, (enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason + ": feature " + (featureEnabledFlag ? "enabled" : "disabled") - + ", long press timeout = " + longPressTimeoutValue); + + ", long press timeout = " + longPressTimeoutMs + " ms"); mNative.setMotionClassifierEnabled(enabled); } - private void updateKeyRepeatInfo(int fallbackKeyRepeatTimeoutValue) { - // Not using ViewConfiguration.getKeyRepeatTimeout here because it may return a stale value. + private void updateKeyRepeatInfo() { + // Use ViewConfiguration getters only as fallbacks because they may return stale values. final int timeoutMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.KEY_REPEAT_TIMEOUT_MS, fallbackKeyRepeatTimeoutValue, + Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(), UserHandle.USER_CURRENT); final int delayMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(), UserHandle.USER_CURRENT); - if (mLastKeyRepeatInfoSettingsUpdate == null || !mLastKeyRepeatInfoSettingsUpdate.isEqualTo( - timeoutMs, delayMs)) { - mNative.setKeyRepeatConfiguration(timeoutMs, delayMs); - mLastKeyRepeatInfoSettingsUpdate = new KeyRepeatInfo(timeoutMs, delayMs); - } - } - - // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value. - private int getLatestLongPressTimeoutValue() { - return Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.LONG_PRESS_TIMEOUT, ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT, - UserHandle.USER_CURRENT); + mNative.setKeyRepeatConfiguration(timeoutMs, delayMs); } private void updateMaximumObscuringOpacityForTouch() { @@ -233,19 +216,4 @@ class InputSettingsObserver extends ContentObserver { } mNative.setMaximumObscuringOpacityForTouch(opacity); } - - private static class KeyRepeatInfo { - private final int mKeyRepeatTimeoutMs; - private final int mKeyRepeatDelayMs; - - private KeyRepeatInfo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) { - this.mKeyRepeatTimeoutMs = keyRepeatTimeoutMs; - this.mKeyRepeatDelayMs = keyRepeatDelayMs; - } - - public boolean isEqualTo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) { - return mKeyRepeatTimeoutMs == keyRepeatTimeoutMs - && mKeyRepeatDelayMs == keyRepeatDelayMs; - } - } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index a46d7197100f..14daf62a9ed2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -56,9 +56,13 @@ public abstract class InputMethodManagerInternal { public abstract void setInteractive(boolean interactive); /** - * Hides the current input method, if visible. + * Hides the input methods for all the users, if visible. + * + * @param reason the reason for hiding the current input method + * @param originatingDisplayId the display ID the request is originated */ - public abstract void hideCurrentInputMethod(@SoftInputShowHideReason int reason); + public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason, + int originatingDisplayId); /** * Returns the list of installed input methods for the specified user. @@ -210,7 +214,8 @@ public abstract class InputMethodManagerInternal { } @Override - public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) { + public void hideAllInputMethods(@SoftInputShowHideReason int reason, + int originatingDisplayId) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6cc069377bf2..ddb32fe09d6c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -226,7 +226,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; + private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035; private static final int MSG_REMOVE_IME_SURFACE = 1060; private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; @@ -4835,7 +4835,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // --------------------------------------------------------- - case MSG_HIDE_CURRENT_INPUT_METHOD: + case MSG_HIDE_ALL_INPUT_METHODS: synchronized (ImfLock.class) { final @SoftInputShowHideReason int reason = (int) msg.obj; hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, @@ -5591,9 +5591,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) { - mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD); - mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget(); + public void hideAllInputMethods(@SoftInputShowHideReason int reason, + int originatingDisplayId) { + mHandler.removeMessages(MSG_HIDE_ALL_INPUT_METHODS); + mHandler.obtainMessage(MSG_HIDE_ALL_INPUT_METHODS, reason).sendToTarget(); } @Override diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java index f49fa6ecca4a..0185190521a3 100644 --- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java @@ -86,8 +86,7 @@ final class SubtypeUtils { continue; } } - if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) - || mode.equalsIgnoreCase(subtype.getMode())) { + if (TextUtils.isEmpty(mode) || mode.equalsIgnoreCase(subtype.getMode())) { return true; } } diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 360a6a721988..6bdfae2dc02f 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -110,7 +110,7 @@ import java.util.Objects; @Override @NonNull - public synchronized MediaRoute2Info getDeviceRoute() { + public synchronized MediaRoute2Info getSelectedRoute() { if (mSelectedRoute != null) { return mSelectedRoute; } diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 7876095a548a..0fdaaa7604e5 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -72,13 +72,9 @@ import com.android.media.flags.Flags; */ boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type); - /** - * Returns currently selected device (built-in or wired) route. - * - * @return non-null device route. - */ + /** Returns the currently selected device (built-in or wired) route. */ @NonNull - MediaRoute2Info getDeviceRoute(); + MediaRoute2Info getSelectedRoute(); /** * Updates device route volume. diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index 6ba40ae33f3c..65874e23dcdc 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -107,7 +107,7 @@ import java.util.Objects; @Override @NonNull - public synchronized MediaRoute2Info getDeviceRoute() { + public synchronized MediaRoute2Info getSelectedRoute() { return mDeviceRoute; } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a158b18d91b4..994d3ca1124f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -76,6 +76,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import android.view.KeyEvent; import com.android.server.LocalServices; @@ -348,16 +349,19 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } else { if (mVolumeControlType == VOLUME_CONTROL_FIXED) { if (DEBUG) { - Log.d(TAG, "Session does not support volume adjustment"); + Slog.d(TAG, "Session does not support volume adjustment"); } } else if (direction == AudioManager.ADJUST_TOGGLE_MUTE || direction == AudioManager.ADJUST_MUTE || direction == AudioManager.ADJUST_UNMUTE) { - Log.w(TAG, "Muting remote playback is not supported"); + Slog.w(TAG, "Muting remote playback is not supported"); } else { if (DEBUG) { - Log.w(TAG, "adjusting volume, pkg=" + packageName + ", asSystemService=" - + asSystemService + ", dir=" + direction); + Slog.w( + TAG, + "adjusting volume, pkg=" + packageName + + ", asSystemService=" + asSystemService + + ", dir=" + direction); } mSessionCb.adjustVolume(packageName, pid, uid, asSystemService, direction); @@ -371,8 +375,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } if (DEBUG) { - Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is " - + mMaxVolume); + Slog.d( + TAG, + "Adjusted optimistic volume to " + mOptimisticVolume + + " max is " + mMaxVolume); } } // Always notify, even if the volume hasn't changed. This is important to ensure that @@ -388,23 +394,33 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mVolumeType == PLAYBACK_TYPE_LOCAL) { int stream = getVolumeStream(mAudioAttrs); final int volumeValue = value; - mHandler.post(new Runnable() { - @Override - public void run() { - try { - mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags, - opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); - } catch (IllegalArgumentException | SecurityException e) { - Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue - + ", flags=" + flags, e); - } - } - }); + mHandler.post( + new Runnable() { + @Override + public void run() { + try { + mAudioManager.setStreamVolumeForUid( + stream, + volumeValue, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } catch (IllegalArgumentException | SecurityException e) { + Slog.e( + TAG, + "Cannot set volume: stream=" + stream + + ", value=" + volumeValue + + ", flags=" + flags, + e); + } + } + }); } else { if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) { if (DEBUG) { - Log.d(TAG, "Session does not support setting volume"); + Slog.d(TAG, "Session does not support setting volume"); } } else { value = Math.max(0, Math.min(value, mMaxVolume)); @@ -419,8 +435,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } if (DEBUG) { - Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is " - + mMaxVolume); + Slog.d( + TAG, + "Set optimistic volume to " + mOptimisticVolume + + " max is " + mMaxVolume); } } // Always notify, even if the volume hasn't changed. @@ -527,12 +545,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public boolean canHandleVolumeKey() { if (isPlaybackTypeLocal()) { + if (DEBUG) { + Slog.d(TAG, "Local MediaSessionRecord can handle volume key"); + } return true; } if (mVolumeControlType == VOLUME_CONTROL_FIXED) { + if (DEBUG) { + Slog.d( + TAG, + "Local MediaSessionRecord with FIXED volume control can't handle volume" + + " key"); + } return false; } if (mVolumeAdjustmentForRemoteGroupSessions) { + if (DEBUG) { + Slog.d( + TAG, + "Volume adjustment for remote group sessions allowed so MediaSessionRecord" + + " can handle volume key"); + } return true; } // See b/228021646 for details. @@ -540,7 +573,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName); boolean foundNonSystemSession = false; boolean remoteSessionAllowVolumeAdjustment = true; + if (DEBUG) { + Slog.d( + TAG, + "Found " + + sessions.size() + + " routing sessions for package name " + + mPackageName); + } for (RoutingSessionInfo session : sessions) { + if (DEBUG) { + Slog.d(TAG, "Found routingSessionInfo: " + session); + } if (!session.isSystemSession()) { foundNonSystemSession = true; if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) { @@ -549,9 +593,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } } if (!foundNonSystemSession) { - Log.d(TAG, "Package " + mPackageName - + " has a remote media session but no associated routing session"); + if (DEBUG) { + Slog.d( + TAG, + "Package " + mPackageName + + " has a remote media session but no associated routing session"); + } } + return foundNonSystemSession && remoteSessionAllowVolumeAdjustment; } @@ -637,8 +686,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR final boolean asSystemService, final boolean useSuggested, final int previousFlagPlaySound) { if (DEBUG) { - Log.w(TAG, "adjusting local volume, stream=" + stream + ", dir=" + direction - + ", asSystemService=" + asSystemService + ", useSuggested=" + useSuggested); + Slog.w( + TAG, + "adjusting local volume, stream=" + stream + ", dir=" + direction + + ", asSystemService=" + asSystemService + + ", useSuggested=" + useSuggested); } // Must use opPackageName for adjusting volumes with UID. final String opPackageName; @@ -653,40 +705,61 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR uid = callingUid; pid = callingPid; } - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (useSuggested) { - if (AudioSystem.isStreamActive(stream, 0)) { - mAudioManager.adjustSuggestedStreamVolumeForUid(stream, - direction, flags, opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); - } else { - mAudioManager.adjustSuggestedStreamVolumeForUid( - AudioManager.USE_DEFAULT_STREAM_TYPE, direction, - flags | previousFlagPlaySound, opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); + mHandler.post( + new Runnable() { + @Override + public void run() { + try { + if (useSuggested) { + if (AudioSystem.isStreamActive(stream, 0)) { + mAudioManager.adjustSuggestedStreamVolumeForUid( + stream, + direction, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } else { + mAudioManager.adjustSuggestedStreamVolumeForUid( + AudioManager.USE_DEFAULT_STREAM_TYPE, + direction, + flags | previousFlagPlaySound, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } + } else { + mAudioManager.adjustStreamVolumeForUid( + stream, + direction, + flags, + opPackageName, + uid, + pid, + mContext.getApplicationInfo().targetSdkVersion); + } + } catch (IllegalArgumentException | SecurityException e) { + Slog.e( + TAG, + "Cannot adjust volume: direction=" + direction + + ", stream=" + stream + ", flags=" + flags + + ", opPackageName=" + opPackageName + ", uid=" + uid + + ", useSuggested=" + useSuggested + + ", previousFlagPlaySound=" + previousFlagPlaySound, + e); } - } else { - mAudioManager.adjustStreamVolumeForUid(stream, direction, flags, - opPackageName, uid, pid, - mContext.getApplicationInfo().targetSdkVersion); } - } catch (IllegalArgumentException | SecurityException e) { - Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream=" - + stream + ", flags=" + flags + ", opPackageName=" + opPackageName - + ", uid=" + uid + ", useSuggested=" + useSuggested - + ", previousFlagPlaySound=" + previousFlagPlaySound, e); - } - } - }); + }); } private void logCallbackException( String msg, ISessionControllerCallbackHolder holder, Exception e) { - Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName - + ", exception=" + e); + Slog.v( + TAG, + msg + ", this=" + this + ", callback package=" + holder.mPackageName + + ", exception=" + e); } private void pushPlaybackStateUpdate() { @@ -1083,7 +1156,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR throw new IllegalArgumentException( "The media button receiver cannot be set to an activity."); } else { - Log.w(TAG, "Ignoring invalid media button receiver targeting an activity."); + Slog.w( + TAG, + "Ignoring invalid media button receiver targeting an activity."); return; } } @@ -1119,7 +1194,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (CompatChanges.isChangeEnabled(THROW_FOR_INVALID_BROADCAST_RECEIVER, uid)) { throw new IllegalArgumentException("Invalid component name: " + receiver); } else { - Log.w( + Slog.w( TAG, "setMediaButtonBroadcastReceiver(): " + "Ignoring invalid component name=" @@ -1258,7 +1333,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (attributes != null) { mAudioAttrs = attributes; } else { - Log.e(TAG, "Received null audio attributes, using existing attributes"); + Slog.e(TAG, "Received null audio attributes, using existing attributes"); } } if (typeChanged) { @@ -1320,7 +1395,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } return true; } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendMediaRequest.", e); + Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } @@ -1343,7 +1418,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } return true; } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendMediaRequest.", e); + Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } @@ -1356,7 +1431,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onCommand(packageName, pid, uid, command, args, cb); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendCommand.", e); + Slog.e(TAG, "Remote failure in sendCommand.", e); } } @@ -1368,7 +1443,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onCustomAction(packageName, pid, uid, action, args); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in sendCustomAction.", e); + Slog.e(TAG, "Remote failure in sendCustomAction.", e); } } @@ -1379,7 +1454,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepare(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepare.", e); + Slog.e(TAG, "Remote failure in prepare.", e); } } @@ -1391,7 +1466,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromMediaId.", e); + Slog.e(TAG, "Remote failure in prepareFromMediaId.", e); } } @@ -1403,7 +1478,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromSearch.", e); + Slog.e(TAG, "Remote failure in prepareFromSearch.", e); } } @@ -1414,7 +1489,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrepareFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in prepareFromUri.", e); + Slog.e(TAG, "Remote failure in prepareFromUri.", e); } } @@ -1425,7 +1500,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlay(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in play.", e); + Slog.e(TAG, "Remote failure in play.", e); } } @@ -1437,7 +1512,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromMediaId.", e); + Slog.e(TAG, "Remote failure in playFromMediaId.", e); } } @@ -1449,7 +1524,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromSearch.", e); + Slog.e(TAG, "Remote failure in playFromSearch.", e); } } @@ -1460,7 +1535,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPlayFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in playFromUri.", e); + Slog.e(TAG, "Remote failure in playFromUri.", e); } } @@ -1471,7 +1546,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSkipToTrack(packageName, pid, uid, id); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in skipToTrack", e); + Slog.e(TAG, "Remote failure in skipToTrack", e); } } @@ -1482,7 +1557,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPause(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in pause.", e); + Slog.e(TAG, "Remote failure in pause.", e); } } @@ -1493,7 +1568,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onStop(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in stop.", e); + Slog.e(TAG, "Remote failure in stop.", e); } } @@ -1504,7 +1579,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onNext(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in next.", e); + Slog.e(TAG, "Remote failure in next.", e); } } @@ -1515,7 +1590,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onPrevious(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in previous.", e); + Slog.e(TAG, "Remote failure in previous.", e); } } @@ -1526,7 +1601,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onFastForward(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in fastForward.", e); + Slog.e(TAG, "Remote failure in fastForward.", e); } } @@ -1537,7 +1612,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onRewind(packageName, pid, uid); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in rewind.", e); + Slog.e(TAG, "Remote failure in rewind.", e); } } @@ -1548,7 +1623,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSeekTo(packageName, pid, uid, pos); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in seekTo.", e); + Slog.e(TAG, "Remote failure in seekTo.", e); } } @@ -1559,7 +1634,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onRate(packageName, pid, uid, rating); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in rate.", e); + Slog.e(TAG, "Remote failure in rate.", e); } } @@ -1570,7 +1645,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSetPlaybackSpeed(packageName, pid, uid, speed); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in setPlaybackSpeed.", e); + Slog.e(TAG, "Remote failure in setPlaybackSpeed.", e); } } @@ -1587,7 +1662,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mCb.onAdjustVolume(packageName, pid, uid, direction); } } catch (RemoteException e) { - Log.e(TAG, "Remote failure in adjustVolume.", e); + Slog.e(TAG, "Remote failure in adjustVolume.", e); } } @@ -1598,7 +1673,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR pid, uid, packageName, reason); mCb.onSetVolumeTo(packageName, pid, uid, value); } catch (RemoteException e) { - Log.e(TAG, "Remote failure in setVolumeTo.", e); + Slog.e(TAG, "Remote failure in setVolumeTo.", e); } } @@ -1641,8 +1716,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb)); mControllerCallbackHolders.add(holder); if (DEBUG) { - Log.d(TAG, "registering controller callback " + cb + " from controller" - + packageName); + Slog.d( + TAG, + "registering controller callback " + cb + + " from controller" + packageName); } // Avoid callback leaks try { @@ -1651,7 +1728,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb.asBinder().linkToDeath(holder.mDeathMonitor, 0); } catch (RemoteException e) { unregisterCallback(cb); - Log.w(TAG, "registerCallback failed to linkToDeath", e); + Slog.w(TAG, "registerCallback failed to linkToDeath", e); } } } @@ -1666,12 +1743,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR cb.asBinder().unlinkToDeath( mControllerCallbackHolders.get(index).mDeathMonitor, 0); } catch (NoSuchElementException e) { - Log.w(TAG, "error unlinking to binder death", e); + Slog.w(TAG, "error unlinking to binder death", e); } mControllerCallbackHolders.remove(index); } if (DEBUG) { - Log.d(TAG, "unregistering callback " + cb.asBinder()); + Slog.d(TAG, "unregistering callback " + cb.asBinder()); } } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 78077a831622..c8dba800a017 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -228,8 +228,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return; } - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); - if (TextUtils.equals(routeId, deviceRoute.getId())) { + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); + if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) { mBluetoothRouteController.transferTo(null); } else { mBluetoothRouteController.transferTo(routeId); @@ -278,11 +278,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return null; } - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( SYSTEM_SESSION_ID, packageName).setSystemSession(true); - builder.addSelectedRoute(deviceRoute.getId()); + builder.addSelectedRoute(selectedDeviceRoute.getId()); for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addTransferableRoute(route.getId()); } @@ -314,7 +314,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); // We must have a device route in the provider info. - builder.addRoute(mDeviceRouteController.getDeviceRoute()); + builder.addRoute(mDeviceRouteController.getSelectedRoute()); for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) { builder.addRoute(route); @@ -338,12 +338,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SYSTEM_SESSION_ID, "" /* clientPackageName */) .setSystemSession(true); - MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); - MediaRoute2Info selectedRoute = deviceRoute; + MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); + MediaRoute2Info selectedRoute = selectedDeviceRoute; MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute(); if (selectedBtRoute != null) { selectedRoute = selectedBtRoute; - builder.addTransferableRoute(deviceRoute.getId()); + builder.addTransferableRoute(selectedDeviceRoute.getId()); } mSelectedRouteId = selectedRoute.getId(); mDefaultRoute = diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 1b7d1ba59b06..9a0b3914122c 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -208,7 +208,7 @@ public class LockdownVpnTracker { // network is the system default. So, if the VPN is up and underlying network // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have // changed to match the new default network (e.g., cell). - mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp); + mVpn.startLegacyVpnPrivileged(mProfile); } catch (IllegalStateException e) { mAcceptedEgressIface = null; Log.e(TAG, "Failed to start VPN", e); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index a1704c6619ad..cb05084ea3f0 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -860,6 +860,9 @@ public class ZenModeHelper { rule.enabled = automaticZenRule.isEnabled(); rule.modified = automaticZenRule.isModified(); rule.zenPolicy = automaticZenRule.getZenPolicy(); + if (Flags.modesApi()) { + rule.zenDeviceEffects = automaticZenRule.getDeviceEffects(); + } rule.zenMode = NotificationManager.zenModeFromInterruptionFilter( automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF); rule.configurationActivity = automaticZenRule.getConfigurationActivity(); @@ -888,6 +891,7 @@ public class ZenModeHelper { .setIconResId(rule.iconResId) .setType(rule.type) .setZenPolicy(rule.zenPolicy) + .setDeviceEffects(rule.zenDeviceEffects) .setEnabled(rule.enabled) .setInterruptionFilter( NotificationManager.zenModeToInterruptionFilter(rule.zenMode)) diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index a8cba532435d..f985b5b64e21 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -355,15 +355,7 @@ public class PersistentDataBlockService extends SystemService { private boolean computeAndWriteDigestLocked() { byte[] digest = computeDigestLocked(null); if (digest != null) { - FileChannel channel; - try { - channel = getBlockOutputChannel(); - } catch (IOException e) { - Slog.e(TAG, "partition not available?", e); - return false; - } - - try { + try (FileChannel channel = getBlockOutputChannel()) { ByteBuffer buf = ByteBuffer.allocate(DIGEST_SIZE_BYTES); buf.put(digest); buf.flip(); @@ -424,8 +416,7 @@ public class PersistentDataBlockService extends SystemService { @VisibleForTesting void formatPartitionLocked(boolean setOemUnlockEnabled) { - try { - FileChannel channel = getBlockOutputChannel(); + try (FileChannel channel = getBlockOutputChannel()) { // Format the data selectively. // // 1. write header, set length = 0 @@ -471,8 +462,7 @@ public class PersistentDataBlockService extends SystemService { private void doSetOemUnlockEnabledLocked(boolean enabled) { - try { - FileChannel channel = getBlockOutputChannel(); + try (FileChannel channel = getBlockOutputChannel()) { channel.position(getBlockDeviceSize() - 1); @@ -554,14 +544,6 @@ public class PersistentDataBlockService extends SystemService { return (int) -maxBlockSize; } - FileChannel channel; - try { - channel = getBlockOutputChannel(); - } catch (IOException e) { - Slog.e(TAG, "partition not available?", e); - return -1; - } - ByteBuffer headerAndData = ByteBuffer.allocate( data.length + HEADER_SIZE + DIGEST_SIZE_BYTES); headerAndData.put(new byte[DIGEST_SIZE_BYTES]); @@ -574,7 +556,7 @@ public class PersistentDataBlockService extends SystemService { return -1; } - try { + try (FileChannel channel = getBlockOutputChannel()) { channel.write(headerAndData); channel.force(true); } catch (IOException e) { @@ -831,8 +813,7 @@ public class PersistentDataBlockService extends SystemService { if (!mIsWritable) { return; } - try { - FileChannel channel = getBlockOutputChannel(); + try (FileChannel channel = getBlockOutputChannel()) { channel.position(offset); channel.write(dataBuffer); channel.force(true); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 5e76ae5cc2c3..11a6d1b8f9a4 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1533,6 +1533,7 @@ public class ComputerEngine implements Computer { ai, flags, state, userId); pi.signingInfo = ps.getSigningInfo(); pi.signatures = getDeprecatedSignatures(pi.signingInfo.getSigningDetails(), flags); + pi.setArchiveTimeMillis(state.getArchiveTimeMillis()); if (DEBUG_PACKAGE_INFO) { Log.v(TAG, "ps.pkg is n/a for [" diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index f45571ab28e4..65c6329587a5 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.content.pm.Flags.sdkLibIndependence; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.content.pm.PackageManager.DELETE_SUCCEEDED; import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES; @@ -606,6 +607,10 @@ final class DeletePackageHelper { firstInstallTime, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, archiveState); + + if ((flags & DELETE_ARCHIVE) != 0) { + ps.modifyUserState(nextUserId).setArchiveTimeMillis(System.currentTimeMillis()); + } } mPm.mSettings.writeKernelMappingLPr(ps); } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index d5dacce3c8ce..eff6157380c0 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -22,6 +22,7 @@ import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; +import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -61,6 +62,7 @@ import android.os.Process; import android.os.SELinux; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ExceptionUtils; import android.util.Slog; import com.android.internal.R; @@ -398,7 +400,45 @@ public class PackageArchiver { packageName))); } - mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage)); + int draftSessionId; + try { + draftSessionId = createDraftSession(packageName, installerPackage, userId); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + throw ExceptionUtils.wrap((IOException) e.getCause()); + } else { + throw e; + } + } + + mPm.mHandler.post( + () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId)); + } + + private int createDraftSession(String packageName, String installerPackage, int userId) { + PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + sessionParams.setAppPackageName(packageName); + sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT; + int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId); + // Handles case of repeated unarchival calls for the same package. + int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid, + sessionParams, + userId); + if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) { + return existingSessionId; + } + + int sessionId = Binder.withCleanCallingIdentity( + () -> mPm.mInstallerService.createSessionInternal( + sessionParams, + installerPackage, mContext.getAttributionTag(), + installerUid, + userId)); + // TODO(b/297358628) Also cleanup sessions upon device restart. + mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId), + getUnarchiveForegroundTimeout()); + return sessionId; } /** @@ -461,7 +501,7 @@ public class PackageArchiver { cloudDrawable.getIntrinsicWidth(), cloudDrawable.getIntrinsicHeight()); LayerDrawable layerDrawable = - new LayerDrawable(new Drawable[] {appIconDrawable, cloudDrawable}); + new LayerDrawable(new Drawable[]{appIconDrawable, cloudDrawable}); final int iconSize = mContext.getSystemService( ActivityManager.class).getLauncherLargeIconSize(); Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize); @@ -487,10 +527,11 @@ public class PackageArchiver { android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}, conditional = true) private void unarchiveInternal(String packageName, UserHandle userHandle, - String installerPackage) { + String installerPackage, int unarchiveId) { int userId = userHandle.getIdentifier(); Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE); unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, unarchiveId); unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName); unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, userId == UserHandle.USER_ALL); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 98eee4dc3b1d..af43a8bec832 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -18,7 +18,9 @@ package com.android.server.pm; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.Process.INVALID_UID; +import static android.os.Process.SYSTEM_UID; import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; @@ -633,17 +635,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements + "to use a data loader"); } + // Draft sessions cannot be created through the public API. + params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE_DRAFT; return createSessionInternal(params, installerPackageName, callingAttributionTag, - userId); + Binder.getCallingUid(), userId); } catch (IOException e) { throw ExceptionUtils.wrap(e); } } - private int createSessionInternal(SessionParams params, String installerPackageName, - String installerAttributionTag, int userId) + int createSessionInternal(SessionParams params, String installerPackageName, + String installerAttributionTag, int callingUid, int userId) throws IOException { - final int callingUid = Binder.getCallingUid(); final Computer snapshot = mPm.snapshotComputer(); snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "createSession"); @@ -692,7 +695,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // initiatingPackageName installerPackageName = SHELL_PACKAGE_NAME; } else { - if (callingUid != Process.SYSTEM_UID) { + if (callingUid != SYSTEM_UID) { // The supplied installerPackageName must always belong to the calling app. mAppOps.checkPackage(callingUid, installerPackageName); } @@ -707,6 +710,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements params.installFlags &= ~PackageManager.INSTALL_FROM_ADB; params.installFlags &= ~PackageManager.INSTALL_ALL_USERS; + params.installFlags &= ~PackageManager.INSTALL_ARCHIVED; params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0 && !mPm.isCallerVerifier(snapshot, callingUid)) { @@ -903,6 +907,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + int requestedInstallerPackageUid = INVALID_UID; + if (requestedInstallerPackageName != null) { + requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName, + 0 /* flags */, userId); + } + if (requestedInstallerPackageUid == INVALID_UID) { + // Requested installer package is invalid, reset it + requestedInstallerPackageName = null; + } + final int sessionId; final PackageInstallerSession session; synchronized (mSessions) { @@ -923,8 +937,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new IllegalStateException( "Too many historical sessions for UID " + callingUid); } + final int existingDraftSessionId = + getExistingDraftSessionId(requestedInstallerPackageUid, params, userId); - sessionId = allocateSessionIdLocked(); + sessionId = existingDraftSessionId != SessionInfo.INVALID_ID ? existingDraftSessionId + : allocateSessionIdLocked(); } final long createdMillis = System.currentTimeMillis(); @@ -945,15 +962,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements params.forceQueryableOverride = false; } } - int requestedInstallerPackageUid = INVALID_UID; - if (requestedInstallerPackageName != null) { - requestedInstallerPackageUid = snapshot.getPackageUid(requestedInstallerPackageName, - 0 /* flags */, userId); - } - if (requestedInstallerPackageUid == INVALID_UID) { - // Requested installer package is invalid, reset it - requestedInstallerPackageName = null; - } final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); if (dpmi != null && dpmi.isUserOrganizationManaged(userId)) { @@ -988,6 +996,68 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements return sessionId; } + int getExistingDraftSessionId(int installerUid, + @NonNull SessionParams sessionParams, int userId) { + synchronized (mSessions) { + return getExistingDraftSessionIdInternal(installerUid, sessionParams, userId); + } + } + + @GuardedBy("mSessions") + private int getExistingDraftSessionIdInternal(int installerUid, + SessionParams sessionParams, int userId) { + String appPackageName = sessionParams.appPackageName; + if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) { + return SessionInfo.INVALID_ID; + } + + PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(appPackageName, + SYSTEM_UID); + if (ps == null || !PackageArchiver.isArchived(ps.getUserStateOrDefault(userId))) { + return SessionInfo.INVALID_ID; + } + + // If unarchiveId is present we match based on it. If unarchiveId is missing we + // choose a draft session too to ensure we don't end up with duplicate sessions + // if the installer doesn't set this field. + if (sessionParams.unarchiveId > 0) { + PackageInstallerSession session = mSessions.get(sessionParams.unarchiveId); + if (session != null + && isValidDraftSession(session, appPackageName, installerUid, userId)) { + return session.sessionId; + } + + return SessionInfo.INVALID_ID; + } + + for (int i = 0; i < mSessions.size(); i++) { + PackageInstallerSession session = mSessions.valueAt(i); + if (session != null + && isValidDraftSession(session, appPackageName, installerUid, userId)) { + return session.sessionId; + } + } + + return SessionInfo.INVALID_ID; + } + + private boolean isValidDraftSession(@NonNull PackageInstallerSession session, + @NonNull String appPackageName, int installerUid, int userId) { + return (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT) != 0 + && appPackageName.equals(session.params.appPackageName) + && session.userId == userId + && installerUid == session.getInstallerUid(); + } + + void cleanupDraftIfUnclaimed(int sessionId) { + synchronized (mSessions) { + PackageInstallerSession session = mPm.mInstallerService.getSession(sessionId); + if (session != null && (session.getInstallFlags() & INSTALL_UNARCHIVE_DRAFT) != 0) { + session.abandon(); + } + } + } + private boolean isStagedInstallerAllowed(String installerName) { return SystemConfig.getInstance().getWhitelistedStagedInstallers().contains(installerName); } @@ -1053,7 +1123,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } private boolean checkOpenSessionAccess(final PackageInstallerSession session) { - if (session == null) { + if (session == null + || (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT) != 0) { return false; } if (isCallingUidOwner(session)) { @@ -1248,10 +1319,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final PackageInstallerSession session = mSessions.valueAt(i); SessionInfo info = - session.generateInfoForCaller(false /*withIcon*/, Process.SYSTEM_UID); + session.generateInfoForCaller(false /*withIcon*/, SYSTEM_UID); if (Objects.equals(info.getInstallerPackageName(), installerPackageName) && session.userId == userId && !session.hasParentSessionId() - && isCallingUidOwner(session)) { + && isCallingUidOwner(session) + && (session.getInstallFlags() & PackageManager.INSTALL_UNARCHIVE_DRAFT) + == 0) { result.add(info); } } @@ -1602,7 +1675,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements PackageInstallerSession session = null; try { var sessionId = createSessionInternal(params, installerPackageName, - null /*installerAttributionTag*/, userId); + null /*installerAttributionTag*/, Binder.getCallingUid(), userId); session = openSessionInternal(sessionId); session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(), null /*signature*/); @@ -2017,7 +2090,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // we don't scrub the data here as this is sent only to the installer several // privileged system packages sendSessionUpdatedBroadcast( - session.generateInfoForCaller(false/*icon*/, Process.SYSTEM_UID), + session.generateInfoForCaller(false/*icon*/, SYSTEM_UID), session.userId); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d38c0a0920b1..47d1df5df1c0 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3586,21 +3586,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.setDontKillApp(false); } + boolean existingSplitReplacedOrRemoved = false; // Inherit splits if not overridden. if (!ArrayUtils.isEmpty(existing.getSplitNames())) { for (int i = 0; i < existing.getSplitNames().length; i++) { final String splitName = existing.getSplitNames()[i]; final File splitFile = new File(existing.getSplitApkPaths()[i]); final boolean splitRemoved = removeSplitList.contains(splitName); - if (!stagedSplits.contains(splitName) && !splitRemoved) { + final boolean splitReplaced = stagedSplits.contains(splitName); + if (!splitReplaced && !splitRemoved) { inheritFileLocked(splitFile); // Collect the requiredSplitTypes and staged splitTypes from splits CollectionUtils.addAll(requiredSplitTypes, existing.getRequiredSplitTypes()[i]); CollectionUtils.addAll(stagedSplitTypes, existing.getSplitTypes()[i]); + } else { + existingSplitReplacedOrRemoved = true; } } } + if (existingSplitReplacedOrRemoved + && (params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) { + // Some splits are being replaced or removed. Make sure the app is restarted. + params.setDontKillApp(false); + } // Inherit compiled oat directory. final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile(); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 3cf5481589fe..72090f24a2ed 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -91,13 +91,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @IntDef({ INSTALL_PERMISSION_FIXED, UPDATE_AVAILABLE, - FORCE_QUERYABLE_OVERRIDE + FORCE_QUERYABLE_OVERRIDE, + SCANNED_AS_STOPPED_SYSTEM_APP }) public @interface Flags { } private static final int INSTALL_PERMISSION_FIXED = 1; private static final int UPDATE_AVAILABLE = 1 << 1; private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2; + private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3; } private int mBooleans; @@ -768,6 +770,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal onChanged(); } + void setArchiveTimeMillis(long value, int userId) { + modifyUserState(userId).setArchiveTimeMillis(value); + onChanged(); + } + boolean getInstalled(int userId) { return readUserState(userId).isInstalled(); } @@ -881,6 +888,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal onChanged(); } + public PackageSetting setScannedAsStoppedSystemApp(boolean stop) { + setBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP, stop); + onChanged(); + return this; + } + boolean getNotLaunched(int userId) { return readUserState(userId).isNotLaunched(); } @@ -1559,6 +1572,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0; } + @Override + public boolean isScannedAsStoppedSystemApp() { + return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP); + } + // Code below generated by codegen v1.0.23. @@ -1707,10 +1725,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1698188444364L, + time = 1700251133016L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\npublic com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index a8196f3f4985..639d6d78f953 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -409,12 +409,17 @@ final class RemovePackageHelper { if (DEBUG_REMOVE) { Slog.d(TAG, "Updating installed state to false because of DELETE_KEEP_DATA"); } + final boolean isArchive = (flags & PackageManager.DELETE_ARCHIVE) != 0; + final long currentTimeMillis = System.currentTimeMillis(); for (int userId : outInfo.mRemovedUsers) { if (DEBUG_REMOVE) { final boolean wasInstalled = deletedPs.getInstalled(userId); Slog.d(TAG, " user " + userId + ": " + wasInstalled + " => " + false); } deletedPs.setInstalled(/* installed= */ false, userId); + if (isArchive) { + deletedPs.modifyUserState(userId).setArchiveTimeMillis(currentTimeMillis); + } } } // make sure to preserve per-user installed state if this removal was just diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index fd70cb13117f..107dc761ceda 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -373,6 +373,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path"; private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path"; + private static final String ATTR_ARCHIVE_TIME = "archive-time"; + private final Handler mHandler; private final PackageManagerTracedLock mLock; @@ -948,6 +950,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ret.getPkgState().setUpdatedSystemApp(false); ret.setTargetSdkVersion(p.getTargetSdkVersion()); ret.setRestrictUpdateHash(p.getRestrictUpdateHash()); + ret.setScannedAsStoppedSystemApp(p.isScannedAsStoppedSystemApp()); } mDisabledSysPackages.remove(name); return ret; @@ -1162,6 +1165,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile Slog.i(PackageManagerService.TAG, "Stopping system package " + pkgName, e); } pkgSetting.setStopped(true, installUserId); + pkgSetting.setScannedAsStoppedSystemApp(true); } if (sharedUser != null) { pkgSetting.setAppId(sharedUser.mAppId); @@ -1929,6 +1933,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ATTR_SPLASH_SCREEN_THEME); final long firstInstallTime = parser.getAttributeLongHex(null, ATTR_FIRST_INSTALL_TIME, 0); + final long archiveTime = parser.getAttributeLongHex(null, + ATTR_ARCHIVE_TIME, 0); final int minAspectRatio = parser.getAttributeInt(null, ATTR_MIN_ASPECT_RATIO, PackageManager.USER_MIN_ASPECT_RATIO_UNSET); @@ -2016,7 +2022,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile firstInstallTime != 0 ? firstInstallTime : origFirstInstallTimes.getOrDefault(name, 0L), minAspectRatio, archiveState); - + ps.setArchiveTimeMillis(archiveTime, userId); mDomainVerificationManager.setLegacyUserState(name, userId, verifState); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); @@ -2379,6 +2385,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME, ustate.getFirstInstallTimeMillis()); + serializer.attributeLongHex(null, ATTR_ARCHIVE_TIME, + ustate.getArchiveTimeMillis()); if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) { serializer.attributeInt(null, ATTR_UNINSTALL_REASON, @@ -3072,6 +3080,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attributeBytesBase64(null, "restrictUpdateHash", pkg.getRestrictUpdateHash()); } + serializer.attributeBoolean(null, "scannedAsStoppedSystemApp", + pkg.isScannedAsStoppedSystemApp()); if (pkg.getLegacyNativeLibraryPath() != null) { serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath()); } @@ -3140,6 +3150,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attributeBytesBase64(null, "restrictUpdateHash", pkg.getRestrictUpdateHash()); } + serializer.attributeBoolean(null, "scannedAsStoppedSystemApp", + pkg.isScannedAsStoppedSystemApp()); if (!pkg.hasSharedUser()) { serializer.attributeInt(null, "userId", pkg.getAppId()); } else { @@ -3873,6 +3885,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0); byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null); + boolean isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null, + "scannedAsStoppedSystemApp", false); int pkgFlags = 0; int pkgPrivateFlags = 0; @@ -3893,7 +3907,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setCpuAbiOverride(cpuAbiOverrideStr) .setLongVersionCode(versionCode) .setTargetSdkVersion(targetSdkVersion) - .setRestrictUpdateHash(restrictUpdateHash); + .setRestrictUpdateHash(restrictUpdateHash) + .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp); long timeStamp = parser.getAttributeLongHex(null, "ft", 0); if (timeStamp == 0) { timeStamp = parser.getAttributeLong(null, "ts", 0); @@ -3988,6 +4003,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile String appMetadataFilePath = null; int targetSdkVersion = 0; byte[] restrictUpdateHash = null; + boolean isScannedAsStoppedSystemApp = false; try { name = parser.getAttributeValue(null, ATTR_NAME); realName = parser.getAttributeValue(null, "realName"); @@ -4028,6 +4044,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile categoryHint = parser.getAttributeInt(null, "categoryHint", ApplicationInfo.CATEGORY_UNDEFINED); appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath"); + isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null, + "scannedAsStoppedSystemApp", false); String domainSetIdString = parser.getAttributeValue(null, "domainSetId"); @@ -4174,7 +4192,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setLoadingCompletedTime(loadingCompletedTime) .setAppMetadataFilePath(appMetadataFilePath) .setTargetSdkVersion(targetSdkVersion) - .setRestrictUpdateHash(restrictUpdateHash); + .setRestrictUpdateHash(restrictUpdateHash) + .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp); // Handle legacy string here for single-user mode final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { @@ -4992,6 +5011,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.append(prefix).append(" queriesIntents=") .println(ps.getPkg().getQueriesIntents()); } + pw.print(prefix); pw.print(" scannedAsStoppedSystemApp="); + pw.println(ps.isScannedAsStoppedSystemApp()); pw.print(prefix); pw.print(" supportsScreens=["); boolean first = true; if (pkg.isSmallScreensSupported()) { @@ -5272,6 +5293,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile date.setTime(pus.getFirstInstallTimeMillis()); pw.println(sdf.format(date)); + pw.print(" archiveTime="); + date.setTime(pus.getArchiveTimeMillis()); + pw.println(sdf.format(date)); + pw.print(" uninstallReason="); pw.println(userState.getUninstallReason()); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 91a70a6b4bdf..926e0188c624 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -152,6 +152,7 @@ public class PackageInfoUtils { info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName(); info.firstInstallTime = firstInstallTime; info.lastUpdateTime = lastUpdateTime; + info.setArchiveTimeMillis(state.getArchiveTimeMillis()); if ((flags & PackageManager.GET_GIDS) != 0) { info.gids = gids; } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 883b0666f979..8bd2d94667f9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -70,6 +70,7 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; +import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.Context; @@ -5327,7 +5328,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt IOnPermissionsChangeListener callback = mPermissionListeners .getBroadcastItem(i); try { - callback.onPermissionsChanged(uid); + callback.onPermissionsChanged(uid, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); } catch (RemoteException e) { Log.e(TAG, "Permission listener is dead", e); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index e7137bbd7969..10b59c7230f6 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -456,4 +456,12 @@ public interface PackageState { @Immutable.Ignore @Nullable byte[] getRestrictUpdateHash(); + + /** + * whether the package has been scanned as a stopped system app. A package will be + * scanned in the stopped state if it is a system app that has a launcher entry and is + * <b>not</b> exempted by {@code <initial-package-state>} tag, and is not an APEX + * @hide + */ + boolean isScannedAsStoppedSystemApp(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2a81a86d20f6..8eb346608732 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -256,4 +256,10 @@ public interface PackageUserState { * @hide */ boolean dataExists(); + + /** + * Timestamp of when the app is archived on the user. + * @hide + */ + long getArchiveTimeMillis(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 2f4ad2d8fcc6..defd3437c14b 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -206,4 +206,9 @@ class PackageUserStateDefault implements PackageUserStateInternal { public boolean dataExists() { return true; } + + @Override + public long getArchiveTimeMillis() { + return 0; + } } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index a76a7ce03170..c0ea7cc0aba2 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -135,6 +135,8 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Nullable private ArchiveState mArchiveState; + private @CurrentTimeMillisLong long mArchiveTimeMillis; + @NonNull final SnapshotCache<PackageUserStateImpl> mSnapshot; @@ -187,6 +189,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt ? null : other.mComponentLabelIconOverrideMap.snapshot(); mFirstInstallTimeMillis = other.mFirstInstallTimeMillis; mArchiveState = other.mArchiveState; + mArchiveTimeMillis = other.mArchiveTimeMillis; mSnapshot = new SnapshotCache.Sealed<>(); } @@ -602,8 +605,6 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt /** * Sets the value for {@link #getArchiveState()}. - * - * @hide */ @NonNull public PackageUserStateImpl setArchiveState(@NonNull ArchiveState archiveState) { @@ -612,6 +613,16 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return this; } + /** + * Sets the timestamp when the app is archived on this user. + */ + @NonNull + public PackageUserStateImpl setArchiveTimeMillis(@CurrentTimeMillisLong long value) { + mArchiveTimeMillis = value; + onChanged(); + return this; + } + @NonNull @Override public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() { @@ -800,6 +811,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member + public @CurrentTimeMillisLong long getArchiveTimeMillis() { + return mArchiveTimeMillis; + } + + @DataClass.Generated.Member public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() { return mSnapshot; } @@ -876,6 +892,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis && watchableEquals(that.mWatchable) && Objects.equals(mArchiveState, that.mArchiveState) + && mArchiveTimeMillis == that.mArchiveTimeMillis && snapshotEquals(that.mSnapshot); } @@ -906,15 +923,16 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis); _hash = 31 * _hash + watchableHashCode(); _hash = 31 * _hash + Objects.hashCode(mArchiveState); + _hash = 31 * _hash + Long.hashCode(mArchiveTimeMillis); _hash = 31 * _hash + snapshotHashCode(); return _hash; } @DataClass.Generated( - time = 1694196888631L, + time = 1699917927942L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nprivate @android.annotation.CurrentTimeMillisLong long mArchiveTimeMillis\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index ed6d3b94758b..532a7f8f893f 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -943,6 +943,12 @@ public class ComponentResolver extends ComponentResolverLocked implements return false; } + if (packageState.isSystem()) { + // A system app can be considered in the stopped state only if it was originally + // scanned in the stopped state. + return packageState.isScannedAsStoppedSystemApp() && + packageState.getUserStateOrDefault(userId).isStopped(); + } return packageState.getUserStateOrDefault(userId).isStopped(); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 72c10cc9a5e8..1cf82bde633d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1073,7 +1073,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void powerPress(long eventTime, int count) { + private void powerPress(long eventTime, int count, int displayId) { // SideFPS still needs to know about suppressed power buttons, in case it needs to block // an auth attempt. if (count == 1) { @@ -1126,8 +1126,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: { if (mDismissImeOnBackKeyPressed) { - InputMethodManagerInternal.get().hideCurrentInputMethod( - SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME); + // TODO(b/308479256): Check if hiding "all" IMEs is OK or not. + InputMethodManagerInternal.get().hideAllInputMethods( + SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, displayId); } else { shortPressPowerGoHome(); } @@ -2662,11 +2663,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onPress(long downTime) { + void onPress(long downTime, int displayId) { if (mShouldEarlyShortPressOnPower) { return; } - powerPress(downTime, 1 /*count*/); + powerPress(downTime, 1 /*count*/, displayId); } @Override @@ -2696,14 +2697,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onMultiPress(long downTime, int count) { - powerPress(downTime, count); + void onMultiPress(long downTime, int count, int displayId) { + powerPress(downTime, count, displayId); } @Override - void onKeyUp(long eventTime, int count) { + void onKeyUp(long eventTime, int count, int displayId) { if (mShouldEarlyShortPressOnPower && count == 1) { - powerPress(eventTime, 1 /*pressCount*/); + powerPress(eventTime, 1 /*pressCount*/, displayId); } } } @@ -2727,7 +2728,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onPress(long downTime) { + void onPress(long downTime, int unusedDisplayId) { mBackKeyHandled |= backKeyPress(); } @@ -2756,7 +2757,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onPress(long downTime) { + void onPress(long downTime, int unusedDisplayId) { if (mShouldEarlyShortPressOnStemPrimary) { return; } @@ -2769,12 +2770,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onMultiPress(long downTime, int count) { + void onMultiPress(long downTime, int count, int unusedDisplayId) { stemPrimaryPress(count); } @Override - void onKeyUp(long eventTime, int count) { + void onKeyUp(long eventTime, int count, int unusedDisplayId) { if (count == 1) { // Save info about the most recent task on the first press of the stem key. This // may be used later to switch to the most recent app using double press gesture. diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 047555ae491c..a060f504b809 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -66,10 +66,12 @@ public final class SingleKeyGestureDetector { * SingleKeyRule rule = * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { * int getMaxMultiPressCount() { // maximum multi press count. } - * void onPress(long downTime) { // short press behavior. } + * void onPress(long downTime, int displayId) { // short press behavior. } * void onLongPress(long eventTime) { // long press behavior. } * void onVeryLongPress(long eventTime) { // very long press behavior. } - * void onMultiPress(long downTime, int count) { // multi press behavior. } + * void onMultiPress(long downTime, int count, int displayId) { + * // multi press behavior. + * } * }; * </pre> */ @@ -114,11 +116,11 @@ public final class SingleKeyGestureDetector { /** * Called when short press has been detected. */ - abstract void onPress(long downTime); + abstract void onPress(long downTime, int displayId); /** * Callback when multi press (>= 2) has been detected. */ - void onMultiPress(long downTime, int count) {} + void onMultiPress(long downTime, int count, int displayId) {} /** * Returns the timeout in milliseconds for a long press. * @@ -148,10 +150,11 @@ public final class SingleKeyGestureDetector { /** * Callback executed upon each key up event that hasn't been processed by long press. * - * @param eventTime the timestamp of this event. - * @param pressCount the number of presses detected leading up to this key up event. + * @param eventTime the timestamp of this event + * @param pressCount the number of presses detected leading up to this key up event + * @param displayId the display ID of the event */ - void onKeyUp(long eventTime, int pressCount) {} + void onKeyUp(long eventTime, int pressCount, int displayId) {} @Override public String toString() { @@ -179,6 +182,10 @@ public final class SingleKeyGestureDetector { } } + private record MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, + int displayId) { + } + static SingleKeyGestureDetector get(Context context, Looper looper) { SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper); sDefaultLongPressTimeout = context.getResources().getInteger( @@ -228,8 +235,9 @@ public final class SingleKeyGestureDetector { mHandledByLongPress = true; mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); - final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, - mActiveRule); + MessageObject object = new MessageObject(mActiveRule, keyCode, /* pressCount= */ 1, + event.getDisplayId()); + final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); } @@ -275,15 +283,17 @@ public final class SingleKeyGestureDetector { if (mKeyPressCounter == 1) { if (mActiveRule.supportLongPress()) { - final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, - mActiveRule); + MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, + event.getDisplayId()); + final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); } if (mActiveRule.supportVeryLongPress()) { - final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, - mActiveRule); + MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, + event.getDisplayId()); + final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); } @@ -299,8 +309,9 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" + " reached the max count " + mKeyPressCounter); } - final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, keyCode, - mKeyPressCounter, mActiveRule); + MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, + event.getDisplayId()); + final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); } @@ -339,9 +350,9 @@ public final class SingleKeyGestureDetector { if (event.getKeyCode() == mActiveRule.mKeyCode) { // key-up action should always be triggered if not processed by long press. - Message msgKeyUp = - mHandler.obtainMessage( - MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule); + MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, + mKeyPressCounter, event.getDisplayId()); + Message msgKeyUp = mHandler.obtainMessage(MSG_KEY_UP, object); msgKeyUp.setAsynchronous(true); mHandler.sendMessage(msgKeyUp); @@ -350,8 +361,9 @@ public final class SingleKeyGestureDetector { if (DEBUG) { Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); } - Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, - 1, mActiveRule); + object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, + /* pressCount= */ 1, event.getDisplayId()); + Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); mActiveRule = null; @@ -360,8 +372,9 @@ public final class SingleKeyGestureDetector { // This could be a multi-press. Wait a little bit longer to confirm. if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) { - Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, - mKeyPressCounter, mActiveRule); + object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, + mKeyPressCounter, event.getDisplayId()); + Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); } @@ -423,20 +436,23 @@ public final class SingleKeyGestureDetector { @Override public void handleMessage(Message msg) { - final SingleKeyRule rule = (SingleKeyRule) msg.obj; + final MessageObject object = (MessageObject) msg.obj; + final SingleKeyRule rule = object.activeRule; if (rule == null) { Log.wtf(TAG, "No active rule."); return; } - final int keyCode = msg.arg1; - final int pressCount = msg.arg2; + final int keyCode = object.keyCode; + final int pressCount = object.pressCount; + final int displayId = object.displayId; switch(msg.what) { case MSG_KEY_UP: if (DEBUG) { - Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode)); + Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode) + + " on display " + displayId); } - rule.onKeyUp(mLastDownTime, pressCount); + rule.onKeyUp(mLastDownTime, pressCount, displayId); break; case MSG_KEY_LONG_PRESS: if (DEBUG) { @@ -454,12 +470,12 @@ public final class SingleKeyGestureDetector { case MSG_KEY_DELAYED_PRESS: if (DEBUG) { Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) - + ", count " + pressCount); + + " on display " + displayId + ", count " + pressCount); } if (pressCount == 1) { - rule.onPress(mLastDownTime); + rule.onPress(mLastDownTime, displayId); } else { - rule.onMultiPress(mLastDownTime, pressCount); + rule.onMultiPress(mLastDownTime, pressCount, displayId); } break; } diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS index 1970ee4b4463..94340ec26cba 100644 --- a/services/core/java/com/android/server/power/OWNERS +++ b/services/core/java/com/android/server/power/OWNERS @@ -2,6 +2,6 @@ michaelwr@google.com santoscordon@google.com philipjunker@google.com -per-file ThermalManagerService.java=wvw@google.com +per-file ThermalManagerService.java=file:/THERMAL_OWNERS per-file LowPowerStandbyController.java=qingxun@google.com -per-file LowPowerStandbyControllerInternal.java=qingxun@google.com
\ No newline at end of file +per-file LowPowerStandbyControllerInternal.java=qingxun@google.com diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index ee3b74653b75..dd39fb02573e 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -32,8 +32,6 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.WorkDuration; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; @@ -197,9 +195,6 @@ public final class HintManagerService extends SystemService { private static native void nativeSetMode(long halPtr, int mode, boolean enabled); - private static native void nativeReportActualWorkDuration(long halPtr, - WorkDuration[] workDurations); - /** Wrapper for HintManager.nativeInit */ public void halInit() { nativeInit(); @@ -257,10 +252,6 @@ public final class HintManagerService extends SystemService { nativeSetMode(halPtr, mode, enabled); } - /** Wrapper for HintManager.nativeReportActualWorkDuration */ - public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) { - nativeReportActualWorkDuration(halPtr, workDurations); - } } @VisibleForTesting @@ -633,52 +624,6 @@ public final class HintManagerService extends SystemService { } } - @Override - public void reportActualWorkDuration2(WorkDuration[] workDurations) { - synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { - return; - } - Preconditions.checkArgument(workDurations.length != 0, "the count" - + " of work durations shouldn't be 0."); - for (WorkDuration workDuration : workDurations) { - validateWorkDuration(workDuration); - } - mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); - } - } - - void validateWorkDuration(WorkDuration workDuration) { - if (DEBUG) { - Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", " - + workDuration.getWorkPeriodStartTimestampNanos() + ", " - + workDuration.getActualTotalDurationNanos() + ", " - + workDuration.getActualCpuDurationNanos() + ", " - + workDuration.getActualGpuDurationNanos() + ")"); - } - if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple( - "Work period start timestamp (%d) should be greater than 0", - workDuration.getWorkPeriodStartTimestampNanos())); - } - if (workDuration.getActualTotalDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", - workDuration.getActualTotalDurationNanos())); - } - if (workDuration.getActualCpuDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0", - workDuration.getActualCpuDurationNanos())); - } - if (workDuration.getActualGpuDurationNanos() < 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual GPU duration (%d) should be non negative", - workDuration.getActualGpuDurationNanos())); - } - } - private void onProcStateChanged(boolean updateAllowed) { updateHintAllowed(updateAllowed); } diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 851a3f7bdaba..83d7d72059b4 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -188,10 +188,12 @@ public class BatteryUsageStatsProvider { } batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_BACKGROUND, getProcessBackgroundTimeMs(uid, realtimeUs)) - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, - getProcessForegroundTimeMs(uid, realtimeUs)); + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND, + getProcessForegroundTimeMs(uid, realtimeUs)) + .setTimeInProcessStateMs(UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, + getProcessForegroundServiceTimeMs(uid, realtimeUs)); } final int[] powerComponents = query.getPowerComponents(); @@ -295,10 +297,14 @@ public class BatteryUsageStatsProvider { } private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { - return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, + return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, + realtimeUs, BatteryStats.STATS_SINCE_CHARGED) + / 1000; + } + + private long getProcessForegroundServiceTimeMs(BatteryStats.Uid uid, long realtimeUs) { + return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, realtimeUs, BatteryStats.STATS_SINCE_CHARGED) - + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, - realtimeUs, BatteryStats.STATS_SINCE_CHARGED)) / 1000; } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index c1da58943062..940feb580a96 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -24,12 +24,14 @@ import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; import static android.hardware.graphics.common.Hdr.DOLBY_VISION; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; +import static android.net.NetworkTemplate.MATCH_PROXY; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.OEM_MANAGED_ALL; import static android.net.NetworkTemplate.OEM_MANAGED_PAID; @@ -488,6 +490,7 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: + case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: @@ -973,21 +976,33 @@ public class StatsPullAtomService extends SystemService { if (DEBUG) { Slog.d(TAG, "Registering NetworkStats pullers with statsd"); } + + boolean canQueryTypeProxy = canQueryNetworkStatsForTypeProxy(); + // Initialize NetworkStats baselines. - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( - FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG)); - mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( - FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER)); - mNetworkStatsBaselines.addAll( - collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER)); + synchronized (mDataBytesTransferLock) { + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG)); + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER)); + mNetworkStatsBaselines.addAll( + collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER)); + if (canQueryTypeProxy) { + mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom( + FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG)); + } + } // Listen to subscription changes to record historical subscriptions that activated before // pulling, this is used by {@code DATA_USAGE_BYTES_TRANSFER}. @@ -1001,6 +1016,9 @@ public class StatsPullAtomService extends SystemService { registerBytesTransferByTagAndMetered(); registerDataUsageBytesTransfer(); registerOemManagedBytesTransfer(); + if (canQueryTypeProxy) { + registerProxyBytesTransferBackground(); + } } private void initAndRegisterDeferredPullers() { @@ -1171,6 +1189,18 @@ public class StatsPullAtomService extends SystemService { } break; } + case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: { + final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate( + new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true); + if (stats != null) { + ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats), + new int[]{TRANSPORT_BLUETOOTH}, + /*slicedByFgbg=*/true, /*slicedByTag=*/false, + /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN, + /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/true)); + } + break; + } case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: { final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate( new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true); @@ -1183,7 +1213,7 @@ public class StatsPullAtomService extends SystemService { new int[]{TRANSPORT_WIFI, TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false, /*slicedByTag=*/true, /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN, - /*subInfo=*/null, OEM_MANAGED_ALL)); + /*subInfo=*/null, OEM_MANAGED_ALL, /*isTypeProxy=*/false)); } break; } @@ -1225,7 +1255,7 @@ public class StatsPullAtomService extends SystemService { final NetworkStatsExt diff = new NetworkStatsExt( removeEmptyEntries(item.stats.subtract(baseline.stats)), item.transports, item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType, - item.subInfo, item.oemManaged); + item.subInfo, item.oemManaged, item.isTypeProxy); // If no diff, skip. if (!diff.stats.iterator().hasNext()) continue; @@ -1363,7 +1393,7 @@ public class StatsPullAtomService extends SystemService { ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats), new int[]{transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/false, /*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN, - /*subInfo=*/null, oemManaged)); + /*subInfo=*/null, oemManaged, /*isTypeProxy=*/false)); } } } @@ -1392,6 +1422,21 @@ public class StatsPullAtomService extends SystemService { } /** + * Check if it is possible to query NetworkStats for TYPE_PROXY. This should only be possible + * if the build includes r.android.com/2828315 + * @return true if querying for TYPE_PROXY is allowed + */ + private static boolean canQueryNetworkStatsForTypeProxy() { + try { + new NetworkTemplate.Builder(MATCH_PROXY).build(); + return true; + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Querying network stats for TYPE_PROXY is not allowed"); + return false; + } + } + + /** * Create a snapshot of NetworkStats since boot for the given template, but add 1 bucket * duration before boot as a buffer to ensure at least one full bucket will be included. * Note that this should be only used to calculate diff since the snapshot might contains @@ -1421,6 +1466,7 @@ public class StatsPullAtomService extends SystemService { final NetworkStats nonTaggedStats = NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats); + queryNonTaggedStats.close(); if (!includeTags) return nonTaggedStats; final android.app.usage.NetworkStats queryTaggedStats = @@ -1429,6 +1475,7 @@ public class StatsPullAtomService extends SystemService { currentTimeInMillis); final NetworkStats taggedStats = NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats); + queryTaggedStats.close(); return nonTaggedStats.add(taggedStats); } @@ -1448,7 +1495,7 @@ public class StatsPullAtomService extends SystemService { ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats), new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true, /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo, - OEM_MANAGED_ALL)); + OEM_MANAGED_ALL, /*isTypeProxy=*/false)); } } return ret; @@ -1598,6 +1645,19 @@ public class StatsPullAtomService extends SystemService { ); } + private void registerProxyBytesTransferBackground() { + int tagId = FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG; + PullAtomMetadata metadata = new PullAtomMetadata.Builder() + .setAdditiveFields(new int[]{3, 4, 5, 6}) + .build(); + mStatsManager.setPullAtomCallback( + tagId, + metadata, + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + private void registerBytesTransferByTagAndMetered() { int tagId = FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED; PullAtomMetadata metadata = new PullAtomMetadata.Builder() diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java index 7dbba0d4337d..512f0bf7ff98 100644 --- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java +++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java @@ -42,15 +42,17 @@ public class NetworkStatsExt { public final int oemManaged; @Nullable public final SubInfo subInfo; + public final boolean isTypeProxy; // True if matching ConnectivityManager#TYPE_PROXY public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) { this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false, - TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, OEM_MANAGED_ALL); + TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, + OEM_MANAGED_ALL, /*isTypeProxy=*/false); } public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg, boolean slicedByTag, boolean slicedByMetered, int ratType, - @Nullable SubInfo subInfo, int oemManaged) { + @Nullable SubInfo subInfo, int oemManaged, boolean isTypeProxy) { this.stats = stats; // Sort transports array so that we can test for equality without considering order. @@ -63,6 +65,7 @@ public class NetworkStatsExt { this.ratType = ratType; this.subInfo = subInfo; this.oemManaged = oemManaged; + this.isTypeProxy = isTypeProxy; } /** @@ -72,6 +75,6 @@ public class NetworkStatsExt { return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered && ratType == other.ratType && Objects.equals(subInfo, other.subInfo) - && oemManaged == other.oemManaged; + && oemManaged == other.oemManaged && isTypeProxy == other.isTypeProxy; } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 3fd832376d2b..7c51e7b84132 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1836,12 +1836,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void hideCurrentInputMethodForBubbles() { + public void hideCurrentInputMethodForBubbles(int displayId) { enforceStatusBarService(); final long token = Binder.clearCallingIdentity(); try { - InputMethodManagerInternal.get().hideCurrentInputMethod( - SoftInputShowHideReason.HIDE_BUBBLES); + // TODO(b/308479256): Check if hiding "all" IMEs is OK or not. + InputMethodManagerInternal.get().hideAllInputMethods( + SoftInputShowHideReason.HIDE_BUBBLES, displayId); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING index 5c37680af745..17d327e94d4d 100644 --- a/services/core/java/com/android/server/timedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING @@ -7,10 +7,7 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] - } - ], - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + }, { "name": "FrameworksTimeServicesTests" } diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING index 63dd7b42f23b..358618a71cbc 100644 --- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING @@ -7,15 +7,15 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "FrameworksTimeServicesTests" } ], // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. "postsubmit": [ { "name": "CtsLocationTimeZoneManagerHostTest" - }, - { - "name": "FrameworksTimeServicesTests" } ] } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 7b399c837d32..faccca8981c8 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -70,7 +70,6 @@ import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; import android.app.PictureInPictureUiState; import android.app.compat.CompatChanges; -import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; import android.app.servertransaction.PipStateTransactionItem; import android.compat.annotation.ChangeId; @@ -1018,7 +1017,7 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), + mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), EnterPipRequestedItem.obtain(r.token)); return true; } catch (Exception e) { @@ -1038,9 +1037,8 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); - transaction.addCallback(PipStateTransactionItem.obtain(r.token, pipState)); - mService.getLifecycleManager().scheduleTransaction(transaction); + mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), + PipStateTransactionItem.obtain(r.token, pipState)); } catch (Exception e) { Slog.w(TAG, "Failed to send pip state transaction item: " + r.intent.getComponent(), e); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 24d99387d63c..081759d563a5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -115,7 +115,6 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static android.view.SurfaceControl.getGlobalTransaction; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -277,7 +276,6 @@ import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ActivityResultItem; -import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.MoveToDisplayItem; @@ -1449,7 +1447,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId, config); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), MoveToDisplayItem.obtain(token, displayId, config)); } catch (RemoteException e) { // If process died, whatever. @@ -1466,7 +1464,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, " + "config: %s", this, config); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), ActivityConfigurationChangeItem.obtain(token, config)); } catch (RemoteException e) { // If process died, whatever. @@ -1487,7 +1485,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b", this, onTop); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), TopResumedActivityChangeItem.obtain(token, onTop)); } catch (RemoteException e) { // If process died, whatever. @@ -1556,9 +1554,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } - // Transition change for the activity moving into a TaskFragment of different bounds. - return newParent.isOrganizedTaskFragment() - && !newParent.getBounds().equals(oldParent.getBounds()); + final boolean isInPip2 = ActivityTaskManagerService.isPip2ExperimentEnabled() + && inPinnedWindowingMode(); + if (!newParent.isOrganizedTaskFragment() && !isInPip2) { + // Parent TaskFragment isn't associated with a TF organizer and we are not in PiP2, + // so do not allow for initializeChangeTransition() on parent changes + return false; + } + // Transition change for the activity moving into TaskFragment of different bounds. + return !newParent.getBounds().equals(oldParent.getBounds()); } @Override @@ -2744,7 +2748,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } try { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), TransferSplashScreenViewStateItem.obtain(token, parcelable, windowAnimationLeash)); scheduleTransferSplashScreenTimeout(); @@ -3908,7 +3912,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), DestroyActivityItem.obtain(token, finishing, configChangeFlags)); } catch (Exception e) { // We can just ignore exceptions here... if the process has crashed, our death @@ -4819,7 +4823,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A try { final ArrayList<ResultInfo> list = new ArrayList<>(); list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), ActivityResultItem.obtain(token, list)); return; } catch (Exception e) { @@ -4830,22 +4834,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Schedule sending results now for Media Projection setup. if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED, STOPPING, STOPPED)) { - final ClientTransaction transaction = ClientTransaction.obtain(app.getThread()); // Build result to be returned immediately. - transaction.addCallback(ActivityResultItem.obtain( - token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data)))); + final ActivityResultItem activityResultItem = ActivityResultItem.obtain( + token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data))); // When the activity result is delivered, the activity will transition to RESUMED. // Since the activity is only resumed so the result can be immediately delivered, // return it to its original lifecycle state. - ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult(); - if (lifecycleItem != null) { - transaction.setLifecycleStateRequest(lifecycleItem); - } else { - Slog.w(TAG, "Unable to get the lifecycle item for state " + mState - + " so couldn't immediately send result"); - } + final ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult(); try { - mAtmService.getLifecycleManager().scheduleTransaction(transaction); + if (lifecycleItem != null) { + mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + app.getThread(), activityResultItem, lifecycleItem); + } else { + Slog.w(TAG, "Unable to get the lifecycle item for state " + mState + + " so couldn't immediately send result"); + mAtmService.getLifecycleManager().scheduleTransactionItem( + app.getThread(), activityResultItem); + } } catch (RemoteException e) { Slog.w(TAG, "Exception thrown sending result to " + this, e); } @@ -4925,7 +4930,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Making sure the client state is RESUMED after transaction completed and doing // so only if activity is currently RESUMED. Otherwise, client may have extra // life-cycle calls to RESUMED (and PAUSED later). - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), NewIntentItem.obtain(token, ar, mState == RESUMED)); unsent = false; } catch (RemoteException e) { @@ -5693,14 +5698,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // can be synchronized with showing the next surface in the transition. if (!usingShellTransitions && !isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) { - SurfaceControl.openTransaction(); - try { - forAllWindows(win -> { - win.mWinAnimator.hide(getGlobalTransaction(), "immediately hidden"); - }, true); - } finally { - SurfaceControl.closeTransaction(); - } + forAllWindows(win -> { + win.mWinAnimator.hide(getPendingTransaction(), "immediately hidden"); + }, true); + scheduleAnimation(); } } @@ -6150,7 +6151,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this), shortComponentName, "userLeaving=false", "make-active"); try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), PauseActivityItem.obtain(token, finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */, mAutoEnteringPip)); } catch (Exception e) { @@ -6163,7 +6164,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setState(STARTED, "makeActiveIfNeeded"); try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), StartActivityItem.obtain(token, takeOptions())); } catch (Exception e) { Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e); @@ -6461,7 +6462,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } EventLogTags.writeWmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), StopActivityItem.obtain(token, configChangeFlags)); mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT); @@ -9901,10 +9902,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { lifecycleItem = PauseActivityItem.obtain(token); } - final ClientTransaction transaction = ClientTransaction.obtain(app.getThread()); - transaction.addCallback(callbackItem); - transaction.setLifecycleStateRequest(lifecycleItem); - mAtmService.getLifecycleManager().scheduleTransaction(transaction); + mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + app.getThread(), callbackItem, lifecycleItem); startRelaunching(); // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only // request resume if this activity is currently resumed, which implies we aren't @@ -9996,7 +9995,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The process will be killed until the activity reports stopped with saved state (see // {@link ActivityTaskManagerService.activityStopped}). try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), StopActivityItem.obtain(token, 0 /* configChanges */)); } catch (RemoteException e) { Slog.w(TAG, "Exception thrown during restart " + this, e); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 009b8e048840..5e0a449ddd63 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -133,6 +133,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.BackgroundActivityStartController.BalCode; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.TaskFragment.EmbeddingCheckResult; @@ -1090,14 +1091,14 @@ class ActivityStarter { ActivityOptions checkedOptions = options != null ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; - @BalCode int balCode = BAL_ALLOW_DEFAULT; + final BalVerdict balVerdict; if (!abort) { try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "shouldAbortBackgroundActivityStart"); BackgroundActivityStartController balController = mSupervisor.getBackgroundActivityLaunchController(); - BackgroundActivityStartController.BalVerdict balVerdict = + balVerdict = balController.checkBackgroundActivityStart( callingUid, callingPid, @@ -1109,13 +1110,13 @@ class ActivityStarter { request.forcedBalByPiSender, intent, checkedOptions); - balCode = balVerdict.getCode(); - request.logMessage.append(" (").append( - BackgroundActivityStartController.balCodeToString(balCode)) - .append(")"); + request.logMessage.append(" (").append(balVerdict).append(")"); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + } else { + // Sets ALLOW_BY_DEFAULT as default value as the activity launch will be aborted anyway. + balVerdict = BalVerdict.ALLOW_BY_DEFAULT; } if (request.allowPendingRemoteAnimationRegistryLookup) { @@ -1293,13 +1294,13 @@ class ActivityStarter { WindowProcessController homeProcess = mService.mHomeProcess; boolean isHomeProcess = homeProcess != null && aInfo.applicationInfo.uid == homeProcess.mUid; - if (balCode != BAL_BLOCK && !isHomeProcess) { + if (balVerdict.allows() && !isHomeProcess) { mService.resumeAppSwitches(); } mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession, request.voiceInteractor, startFlags, checkedOptions, - inTask, inTaskFragment, balCode, intentGrants, realCallingUid); + inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid); if (request.outActivity != null) { request.outActivity[0] = mLastStartActivityRecord; @@ -1449,7 +1450,8 @@ class ActivityStarter { private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, ActivityOptions options, Task inTask, - TaskFragment inTaskFragment, @BalCode int balCode, + TaskFragment inTaskFragment, + BalVerdict balVerdict, NeededUriGrants intentGrants, int realCallingUid) { int result = START_CANCELED; final Task startedActivityRootTask; @@ -1468,7 +1470,7 @@ class ActivityStarter { try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner"); result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, options, inTask, inTaskFragment, balCode, + startFlags, options, inTask, inTaskFragment, balVerdict, intentGrants, realCallingUid); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); @@ -1615,10 +1617,10 @@ class ActivityStarter { int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, ActivityOptions options, Task inTask, - TaskFragment inTaskFragment, @BalCode int balCode, + TaskFragment inTaskFragment, BalVerdict balVerdict, NeededUriGrants intentGrants, int realCallingUid) { setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord, - voiceSession, voiceInteractor, balCode, realCallingUid); + voiceSession, voiceInteractor, balVerdict.getCode(), realCallingUid); computeLaunchingTaskFlags(); mIntent.setFlags(mLaunchFlags); @@ -1696,7 +1698,8 @@ class ActivityStarter { } recordTransientLaunchIfNeeded(targetTaskTop); // Recycle the target task for this launch. - startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants); + startResult = + recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict); if (startResult != START_SUCCESS) { return startResult; } @@ -1730,6 +1733,7 @@ class ActivityStarter { recordTransientLaunchIfNeeded(mLastStartActivityRecord); if (!mAvoidMoveToFront && mDoResume) { + logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask); mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask); if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming() && !dreamStopping) { @@ -1800,6 +1804,7 @@ class ActivityStarter { // now update the focused root-task accordingly. if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) { + logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask); mTargetRootTask.moveToFront("startActivityInner"); } mRootWindowContainer.resumeFocusedTasksTopActivities( @@ -1817,7 +1822,7 @@ class ActivityStarter { // Note that mStartActivity and source should be in the same Task at this point. if (mOptions != null && mOptions.isLaunchIntoPip() && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask() - && balCode != BAL_BLOCK) { + && balVerdict.allows()) { mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity, sourceRecord, "launch-into-pip"); } @@ -1828,6 +1833,24 @@ class ActivityStarter { return START_SUCCESS; } + private void logOnlyCreatorAllowsBAL(BalVerdict balVerdict, + int realCallingUid, boolean newTask) { + // TODO (b/296478675) eventually, we will prevent such case from happening + // and probably also log that a BAL is prevented by android V. + if (!newTask && balVerdict.onlyCreatorAllows()) { + String realCallingPackage = + mService.mContext.getPackageManager().getNameForUid(realCallingUid); + if (realCallingPackage == null) { + realCallingPackage = "uid=" + realCallingUid; + } + Slog.wtf(TAG, "A background app is brought to the foreground due to a " + + "PendingIntent. However, only the creator of the PendingIntent allows BAL, " + + "while the sender does not allow BAL. realCallingPackage: " + + realCallingPackage + "; callingPackage: " + mRequest.callingPackage + + "; mTargetRootTask:" + mTargetRootTask); + } + } + private void recordTransientLaunchIfNeeded(ActivityRecord r) { if (r == null || !mTransientLaunch) return; final TransitionController controller = r.mTransitionController; @@ -1995,7 +2018,7 @@ class ActivityStarter { */ @VisibleForTesting int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask, - NeededUriGrants intentGrants) { + NeededUriGrants intentGrants, BalVerdict balVerdict) { // Should not recycle task which is from a different user, just adding the starting // activity to the task. if (targetTask.mUserId != mStartActivity.mUserId) { @@ -2024,7 +2047,7 @@ class ActivityStarter { mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */, targetTaskTop); - setTargetRootTaskIfNeeded(targetTaskTop); + setTargetRootTaskIfNeeded(targetTaskTop, balVerdict); // When there is a reused activity and the current result is a trampoline activity, // set the reused activity as the result. @@ -2040,6 +2063,7 @@ class ActivityStarter { if (!mMovedToFront && mDoResume) { ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask, targetTaskTop); + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveToFront("intentActivityFound"); } resumeTargetRootTaskIfNeeded(); @@ -2068,6 +2092,7 @@ class ActivityStarter { targetTaskTop.showStartingWindow(true /* taskSwitch */); } else if (mDoResume) { // Make sure the root task and its belonging display are moved to topmost. + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveToFront("intentActivityFound"); } // We didn't do anything... but it was needed (a.k.a., client don't use that intent!) @@ -2663,7 +2688,7 @@ class ActivityStarter { * @param intentActivity Existing matching activity. * @return {@link ActivityRecord} brought to front. */ - private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) { + private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity, BalVerdict balVerdict) { intentActivity.getTaskFragment().clearLastPausedActivity(); Task intentTask = intentActivity.getTask(); // The intent task might be reparented while in getOrCreateRootTask, caches the original @@ -2730,6 +2755,7 @@ class ActivityStarter { // task on top there. // Defer resuming the top activity while moving task to top, since the // current task-top activity may not be the activity that should be resumed. + logOnlyCreatorAllowsBAL(balVerdict, mRealCallingUid, false); mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, DEFER_RESUME, "bringingFoundTaskToFront"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3ec58f05ac96..34c7eee45f34 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -125,6 +125,7 @@ import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_R import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT; import static com.android.server.wm.WindowManagerService.MY_PID; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; +import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; import android.Manifest; import android.annotation.IntDef; @@ -165,6 +166,7 @@ import android.app.assist.ActivityId; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.compat.CompatChanges; +import android.app.sdksandbox.sandboxactivity.SdkSandboxActivityAuthority; import android.app.usage.UsageStatsManagerInternal; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -279,6 +281,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wallpaper.WallpaperManagerInternal; +import com.android.wm.shell.Flags; import java.io.BufferedReader; import java.io.File; @@ -314,8 +317,6 @@ import java.util.Set; public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender"; private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; - private static final String ENABLE_PIP2_IMPLEMENTATION = - "persist.wm.debug.enable_pip2_implementation"; static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; @@ -1260,6 +1261,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { true /*validateIncomingUser*/); } + static boolean isSdkSandboxActivity(Context context, Intent intent) { + return intent != null + && (sandboxActivitySdkBasedContext() + ? SdkSandboxActivityAuthority.isSdkSandboxActivity(context, intent) + : intent.isSandboxActivity(context)); + } + private int startActivityAsUser(IApplicationThread caller, String callingPackage, @Nullable String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, @@ -1270,7 +1278,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityAsUser"); - if (intent != null && intent.isSandboxActivity(mContext)) { + if (isSdkSandboxActivity(mContext, intent)) { SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager( SdkSandboxManagerLocal.class); sdkSandboxManagerLocal.enforceAllowedToHostSandboxedActivity( @@ -7253,6 +7261,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } static boolean isPip2ExperimentEnabled() { - return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); + return Flags.enablePip2Implementation(); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index e196d463db79..90eeed288240 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -98,7 +98,6 @@ import android.app.ResultInfo; import android.app.TaskInfo; import android.app.WaitResult; import android.app.servertransaction.ActivityLifecycleItem; -import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.LaunchActivityItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.ResumeActivityItem; @@ -928,14 +927,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // Create activity launch transaction. - final ClientTransaction clientTransaction = ClientTransaction.obtain( - proc.getThread()); - final boolean isTransitionForward = r.isTransitionForward(); final IBinder fragmentToken = r.getTaskFragment().getFragmentToken(); - final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); - clientTransaction.addCallback(LaunchActivityItem.obtain(r.token, + final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token, r.intent, System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. @@ -945,7 +940,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(), results, newIntents, r.takeOptions(), isTransitionForward, proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController, - r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken)); + r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken); // Set desired final state. final ActivityLifecycleItem lifecycleItem; @@ -955,10 +950,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } else { lifecycleItem = PauseActivityItem.obtain(r.token); } - clientTransaction.setLifecycleStateRequest(lifecycleItem); // Schedule transaction. - mService.getLifecycleManager().scheduleTransaction(clientTransaction); + mService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + proc.getThread(), launchActivityItem, lifecycleItem); if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) { // If the seq is increased, there should be something changed (e.g. registered @@ -1089,7 +1084,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Remove the process record so it won't be considered as alive. mService.mProcessNames.remove(wpc.mName, wpc.mUid); mService.mProcessMap.remove(wpc.getPid()); - } else if (r.intent.isSandboxActivity(mService.mContext)) { + } else if (ActivityTaskManagerService.isSdkSandboxActivity(mService.mContext, r.intent)) { Slog.e(TAG, "Abort sandbox activity launching as no sandbox process to host it."); r.finishIfPossible("No sandbox process for the activity", false /* oomAdj */); r.launchFailed = true; @@ -1633,7 +1628,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } - private void removeRootTaskInSurfaceTransaction(Task rootTask) { + /** + * Removes the root task associated with the given {@param rootTask}. If the {@param rootTask} + * is the pinned task, then its child tasks are not explicitly removed when the root task is + * destroyed, but instead moved back onto the TaskDisplayArea. + */ + void removeRootTask(Task rootTask) { if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) { removePinnedRootTaskInSurfaceTransaction(rootTask); } else { @@ -1644,15 +1644,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } /** - * Removes the root task associated with the given {@param task}. If the {@param task} is the - * pinned task, then its child tasks are not explicitly removed when the root task is - * destroyed, but instead moved back onto the TaskDisplayArea. - */ - void removeRootTask(Task task) { - mWindowManager.inSurfaceTransaction(() -> removeRootTaskInSurfaceTransaction(task)); - } - - /** * Removes the task with the specified task id. * * @param taskId Identifier of the task to be removed. diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 805bff240f66..05087f8a6edf 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -71,7 +71,6 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITIO import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -82,7 +81,6 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; -import android.util.Slog; import android.view.Display; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; @@ -1179,16 +1177,7 @@ public class AppTransitionController { } app.updateReportedVisibilityLocked(); app.waitingToShow = false; - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION handleAppTransitionReady()"); - mService.openSurfaceTransaction(); - try { - app.showAllWindowsLocked(); - } finally { - mService.closeSurfaceTransaction("handleAppTransitionReady"); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION handleAppTransitionReady()"); - } + app.showAllWindowsLocked(); if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) { app.attachThumbnailAnimation(); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index a3e1c8c90d32..287aaf9da42a 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -433,10 +433,15 @@ public class BackgroundActivityStartController { static class BalVerdict { static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); + static final BalVerdict ALLOW_BY_DEFAULT = + new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default"); private final @BalCode int mCode; private final boolean mBackground; private final String mMessage; private String mProcessInfo; + // indicates BAL would be blocked because only creator of the PI has the privilege to allow + // BAL, the sender does not have the privilege to allow BAL. + private boolean mOnlyCreatorAllows; BalVerdict(@BalCode int balCode, boolean background, String message) { this.mBackground = background; @@ -457,6 +462,15 @@ public class BackgroundActivityStartController { return !blocks(); } + BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) { + mOnlyCreatorAllows = onlyCreatorAllows; + return this; + } + + boolean onlyCreatorAllows() { + return mOnlyCreatorAllows; + } + public String toString() { StringBuilder builder = new StringBuilder(); builder.append(balCodeToString(mCode)); @@ -545,18 +559,15 @@ public class BackgroundActivityStartController { BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); if (!state.hasRealCaller()) { + BalVerdict resultForRealCaller = null; // nothing to compute if (resultForCaller.allows()) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed. " - + state.dump(resultForCaller)); + + state.dump(resultForCaller, resultForRealCaller)); } return statsLog(resultForCaller, state); } - // anything that has fallen through would currently be aborted - Slog.w(TAG, "Background activity launch blocked! " - + state.dump(resultForCaller)); - showBalBlockedToast("BAL blocked", state); - return statsLog(BalVerdict.BLOCK, state); + return abortLaunch(state, resultForCaller, resultForRealCaller); } // The realCaller result is only calculated for PendingIntents (indicated by a valid @@ -569,6 +580,10 @@ public class BackgroundActivityStartController { BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows() ? resultForCaller : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); + if (state.isPendingIntent()) { + resultForCaller.setOnlyCreatorAllows( + resultForCaller.allows() && resultForRealCaller.blocks()); + } if (resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() @@ -588,11 +603,13 @@ public class BackgroundActivityStartController { } return statsLog(resultForRealCaller, state); } - if (resultForCaller.allows() && resultForRealCaller.allows() + boolean callerCanAllow = resultForCaller.allows() && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() - == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; + boolean realCallerCanAllow = resultForRealCaller.allows() && checkedOptions.getPendingIntentBackgroundActivityStartMode() - == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; + if (callerCanAllow && realCallerCanAllow) { // Both caller and real caller allow with system defined behavior if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) { Slog.wtf(TAG, @@ -608,10 +625,9 @@ public class BackgroundActivityStartController { "Without Android 15 BAL hardening this activity start would be allowed" + " (missing opt in by PI creator or sender)! " + state.dump(resultForCaller, resultForRealCaller)); - // fall through to abort - } else if (resultForCaller.allows() - && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() - == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + return abortLaunch(state, resultForCaller, resultForRealCaller); + } + if (callerCanAllow) { // Allowed before V by creator if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) { Slog.wtf(TAG, @@ -626,10 +642,9 @@ public class BackgroundActivityStartController { "Without Android 15 BAL hardening this activity start would be allowed" + " (missing opt in by PI creator)! " + state.dump(resultForCaller, resultForRealCaller)); - // fall through to abort - } else if (resultForRealCaller.allows() - && checkedOptions.getPendingIntentBackgroundActivityStartMode() - == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + return abortLaunch(state, resultForCaller, resultForRealCaller); + } + if (realCallerCanAllow) { // Allowed before U by sender if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) { Slog.wtf(TAG, @@ -643,9 +658,14 @@ public class BackgroundActivityStartController { Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - // fall through to abort + return abortLaunch(state, resultForCaller, resultForRealCaller); } - // anything that has fallen through would currently be aborted + // neither the caller not the realCaller can allow or have explicitly opted out + return abortLaunch(state, resultForCaller, resultForRealCaller); + } + + private BalVerdict abortLaunch(BalState state, BalVerdict resultForCaller, + BalVerdict resultForRealCaller) { Slog.w(TAG, "Background activity launch blocked! " + state.dump(resultForCaller, resultForRealCaller)); showBalBlockedToast("BAL blocked", state); diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index ef31837f810b..28f656e624fb 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -40,35 +40,51 @@ class ClientLifecycleManager { * @throws RemoteException * * @see ClientTransaction + * @deprecated use {@link #scheduleTransactionItem(IApplicationThread, ClientTransactionItem)}. */ + @Deprecated void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException { final IApplicationThread client = transaction.getClient(); - transaction.schedule(); - if (!(client instanceof Binder)) { - // If client is not an instance of Binder - it's a remote call and at this point it is - // safe to recycle the object. All objects used for local calls will be recycled after - // the transaction is executed on client in ActivityThread. - transaction.recycle(); + try { + transaction.schedule(); + } finally { + if (!(client instanceof Binder)) { + // If client is not an instance of Binder - it's a remote call and at this point it + // is safe to recycle the object. All objects used for local calls will be recycled + // after the transaction is executed on client in ActivityThread. + transaction.recycle(); + } } } /** * Schedules a single transaction item, either a callback or a lifecycle request, delivery to * client application. - * @param client Target client. - * @param transactionItem A transaction item to deliver a message. * @throws RemoteException - * * @see ClientTransactionItem */ - void scheduleTransaction(@NonNull IApplicationThread client, + void scheduleTransactionItem(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem) throws RemoteException { final ClientTransaction clientTransaction = ClientTransaction.obtain(client); - if (transactionItem instanceof ActivityLifecycleItem) { + if (transactionItem.isActivityLifecycleItem()) { clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); } else { clientTransaction.addCallback(transactionItem); } scheduleTransaction(clientTransaction); } + + /** + * Schedules a single transaction item with a lifecycle request, delivery to client application. + * @throws RemoteException + * @see ClientTransactionItem + */ + void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client, + @NonNull ClientTransactionItem transactionItem, + @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException { + final ClientTransaction clientTransaction = ClientTransaction.obtain(client); + clientTransaction.addCallback(transactionItem); + clientTransaction.setLifecycleStateRequest(lifecycleItem); + scheduleTransaction(clientTransaction); + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 49248107a004..2c224e458a2d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1004,6 +1004,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpApplySurfaceChangesTransactionState.obscured; final RootWindowContainer root = mWmService.mRoot; + if (w.mHasSurface) { + // Take care of the window being ready to display. + final boolean committed = w.mWinAnimator.commitFinishDrawingLocked(); + if (isDefaultDisplay && committed) { + if (w.hasWallpaper()) { + ProtoLog.v(WM_DEBUG_WALLPAPER, + "First draw done in potential wallpaper target %s", w); + mWallpaperMayChange = true; + pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + if (DEBUG_LAYOUT_REPEATS) { + surfacePlacer.debugLayoutRepeats( + "wallpaper and commitFinishDrawingLocked true", + pendingLayoutChanges); + } + } + } + } + // Update effect. w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured; @@ -1090,30 +1108,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp w.handleWindowMovedIfNeeded(); - final WindowStateAnimator winAnimator = w.mWinAnimator; - //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); w.resetContentChanged(); - // Moved from updateWindowsAndWallpaperLocked(). - if (w.mHasSurface) { - // Take care of the window being ready to display. - final boolean committed = winAnimator.commitFinishDrawingLocked(); - if (isDefaultDisplay && committed) { - if (w.hasWallpaper()) { - ProtoLog.v(WM_DEBUG_WALLPAPER, - "First draw done in potential wallpaper target %s", w); - mWallpaperMayChange = true; - pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - if (DEBUG_LAYOUT_REPEATS) { - surfacePlacer.debugLayoutRepeats( - "wallpaper and commitFinishDrawingLocked true", - pendingLayoutChanges); - } - } - } - } - final ActivityRecord activity = w.mActivityRecord; if (activity != null && activity.isVisibleRequested()) { activity.updateLetterboxSurface(w); @@ -5587,12 +5584,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void prepareSurfaces() { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces"); try { - final Transaction transaction = getPendingTransaction(); super.prepareSurfaces(); - - // TODO: Once we totally eliminate global transaction we will pass transaction in here - // rather than merging to global. - SurfaceControl.mergeToGlobalTransaction(transaction); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 534cdc2015e3..e808decc354d 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -38,7 +38,6 @@ import static com.android.server.wm.DisplayRotationReversionController.REVERSION import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; -import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.RefreshCallbackItem; import android.app.servertransaction.ResumeActivityItem; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -226,13 +225,12 @@ final class DisplayRotationCompatPolicy { ProtoLog.v(WM_DEBUG_STATES, "Refreshing activity for camera compatibility treatment, " + "activityRecord=%s", activity); - final ClientTransaction transaction = ClientTransaction.obtain( - activity.app.getThread()); - transaction.addCallback(RefreshCallbackItem.obtain(activity.token, - cycleThroughStop ? ON_STOP : ON_PAUSE)); - transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(activity.token, - /* isForward */ false, /* shouldSendCompatFakeFocus */ false)); - activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction); + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain( + activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( + activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + activity.app.getThread(), refreshCallbackItem, resumeActivityItem); mHandler.postDelayed( () -> onActivityRefreshed(activity), REFRESH_CALLBACK_TIMEOUT_MS); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 997b6084f6e2..14912d041127 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -439,8 +439,10 @@ final class InputMonitor { final InputMethodManagerInternal inputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); if (inputMethodManagerInternal != null) { - inputMethodManagerInternal.hideCurrentInputMethod( - SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); + // TODO(b/308479256): Check if hiding "all" IMEs is OK or not. + inputMethodManagerInternal.hideAllInputMethods( + SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, + mDisplayContent.getDisplayId()); } // Ensure removing the IME snapshot when the app no longer to show on the // task snapshot (also taking the new task snaphot to update the overview). diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index b738c1c4bd40..5269d35529bd 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -307,7 +307,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan mService.stopAppSwitches(); } - mWindowManager.inSurfaceTransaction(() -> { + inSurfaceTransaction(() -> { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#onAnimationFinished_inSurfaceTransaction"); mService.deferWindowLayout(); @@ -419,6 +419,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan } } + // No-op wrapper to keep legacy code. + private static void inSurfaceTransaction(Runnable exec) { + exec.run(); + } + /** Gives the owner of recents animation higher priority. */ private void setProcessAnimating(boolean animating) { if (mCaller == null) return; diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index eb639b6f2033..a98b9f7cd1d1 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -307,7 +307,6 @@ class RemoteAnimationController implements DeathRecipient { mIsFinishing = true; unlinkToDeathOfRunner(); releaseFinishedCallback(); - mService.openSurfaceTransaction(); try { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): Notify animation finished:"); @@ -348,7 +347,6 @@ class RemoteAnimationController implements DeathRecipient { Slog.e(TAG, "Failed to finish remote animation", e); throw e; } finally { - mService.closeSurfaceTransaction("RemoteAnimationController#finished"); mIsFinishing = false; } // Reset input for all activities when the remote animation is finished. diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index fe2c2504abd9..0c235bae2006 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -75,7 +75,6 @@ import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT; @@ -788,23 +787,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final DisplayContent defaultDisplay = mWmService.getDefaultDisplayContentLocked(); final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked; - if (SHOW_LIGHT_TRANSACTIONS) { - Slog.i(TAG, - ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); - } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges"); - mWmService.openSurfaceTransaction(); try { applySurfaceChangesTransaction(); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { - mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces"); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - if (SHOW_LIGHT_TRANSACTIONS) { - Slog.i(TAG, - "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); - } } // Send any pending task-info changes that were queued-up during a layout deferment @@ -998,9 +987,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Give the display manager a chance to adjust properties like display rotation if it needs // to. mWmService.mDisplayManagerInternal.performTraversal(t); - if (t != defaultDc.mSyncTransaction) { - SurfaceControl.mergeToGlobalTransaction(t); - } } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 267d148b1ce8..73755121daf8 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4545,12 +4545,6 @@ class Task extends TaskFragment { * @param creating {@code true} if this is being run during task construction. */ void setWindowingMode(int preferredWindowingMode, boolean creating) { - mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction( - preferredWindowingMode, creating)); - } - - private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, - boolean creating) { final TaskDisplayArea taskDisplayArea = getDisplayArea(); if (taskDisplayArea == null) { Slog.d(TAG, "taskDisplayArea is null, bail early"); @@ -4625,7 +4619,12 @@ class Task extends TaskFragment { if (topActivity != null) { mTaskSupervisor.mNoAnimActivities.add(topActivity); } - super.setWindowingMode(windowingMode); + + final boolean isPip2ExperimentEnabled = + ActivityTaskManagerService.isPip2ExperimentEnabled(); + if (!isPip2ExperimentEnabled) { + super.setWindowingMode(windowingMode); + } if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) { // Try reparent pinned activity back to its original task after @@ -4634,32 +4633,37 @@ class Task extends TaskFragment { // PiP, we set final windowing mode on the ActivityRecord first and then on its // Task when the exit PiP transition finishes. Meanwhile, the exit transition is // always performed on its original task, reparent immediately in ActivityRecord - // breaks it. - if (topActivity.getLastParentBeforePip() != null) { - // Do not reparent if the pinned task is in removal, indicated by the - // force hidden flag. - if (!isForceHidden()) { - final Task lastParentBeforePip = topActivity.getLastParentBeforePip(); - if (lastParentBeforePip.isAttached()) { - topActivity.reparent(lastParentBeforePip, - lastParentBeforePip.getChildCount() /* top */, - "movePinnedActivityToOriginalTask"); - final DisplayContent dc = topActivity.getDisplayContent(); - if (dc != null && dc.isFixedRotationLaunchingApp(topActivity)) { - // Expanding pip into new rotation, so create a rotation leash - // until the display is rotated. - topActivity.getOrCreateFixedRotationLeash( - topActivity.getPendingTransaction()); - } - lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask"); - } + // breaks it. Do not reparent if the pinned task is in removal, indicated by the + // force hidden flag. + if (topActivity.getLastParentBeforePip() != null && !isForceHidden() + && topActivity.getLastParentBeforePip().isAttached()) { + // We need to collect the pip activity to allow for screenshots + // to be taken as a part of reparenting. + mTransitionController.collect(topActivity); + + final Task lastParentBeforePip = topActivity.getLastParentBeforePip(); + topActivity.reparent(lastParentBeforePip, + lastParentBeforePip.getChildCount() /* top */, + "movePinnedActivityToOriginalTask"); + final DisplayContent dc = topActivity.getDisplayContent(); + if (dc != null && dc.isFixedRotationLaunchingApp(topActivity)) { + // Expanding pip into new rotation, so create a rotation leash + // until the display is rotated. + topActivity.getOrCreateFixedRotationLeash( + topActivity.getSyncTransaction()); } + lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask"); + } + if (isPip2ExperimentEnabled) { + super.setWindowingMode(windowingMode); } // Resume app-switches-allowed flag when exiting from pinned mode since // it does not follow the ActivityStarter path. if (topActivity.shouldBeVisible()) { mAtmService.resumeAppSwitches(); } + } else if (isPip2ExperimentEnabled) { + super.setWindowingMode(windowingMode); } if (creating) { @@ -5966,18 +5970,16 @@ class Task extends TaskFragment { "Can't exit pinned mode if it's not pinned already."); } - mWmService.inSurfaceTransaction(() -> { - final Task task = getBottomMostTask(); - setWindowingMode(WINDOWING_MODE_UNDEFINED); + final Task task = getBottomMostTask(); + setWindowingMode(WINDOWING_MODE_UNDEFINED); - // Task could have been removed from the hierarchy due to windowing mode change - // where its only child is reparented back to their original parent task. - if (isAttached()) { - getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); - } + // Task could have been removed from the hierarchy due to windowing mode change + // where its only child is reparented back to their original parent task. + if (isAttached()) { + getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + } - mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this); - }); + mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this); } private int setBounds(Rect existing, Rect bounds) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 197edc30aa8e..8bc461f05387 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1793,7 +1793,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), prev.shortComponentName, "userLeaving=" + userLeaving, reason); - mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), + mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving, prev.configChangeFlags, pauseImmediately, autoEnteringPip)); } catch (Exception e) { diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index f1481760a5e7..17ab00d64924 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -394,7 +394,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean taskAppearedSent = t.mTaskAppearedSent; if (taskAppearedSent) { if (t.getSurfaceControl() != null) { - t.migrateToNewSurfaceControl(t.getPendingTransaction()); + t.migrateToNewSurfaceControl(t.getSyncTransaction()); } t.mTaskAppearedSent = false; } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index e95d2651504b..fd22f15fb798 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -128,7 +128,6 @@ public class WindowAnimator { } ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate"); - mService.openSurfaceTransaction(); try { // Remove all deferred displays, tasks, and activities. root.handleCompleteDeferredRemoval(); @@ -163,6 +162,7 @@ public class WindowAnimator { dc.mLastContainsRunningSurfaceAnimator = false; dc.enableHighFrameRate(false); } + mTransaction.merge(dc.getPendingTransaction()); } cancelAnimation(); @@ -196,8 +196,8 @@ public class WindowAnimator { updateRunningExpensiveAnimationsLegacy(); } - SurfaceControl.mergeToGlobalTransaction(mTransaction); - mService.closeSurfaceTransaction("WindowAnimator"); + mTransaction.apply(); + mService.mWindowTracing.logState("WindowAnimator"); ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2b77ffffece0..e28262dfbe2f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2988,12 +2988,23 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** Whether we can start change transition with this window and current display status. */ boolean canStartChangeTransition() { - return !mWmService.mDisableTransitionAnimation && mDisplayContent != null - && getSurfaceControl() != null && !mDisplayContent.inTransition() - && isVisible() && isVisibleRequested() && okToAnimate() - // Pip animation will be handled by PipTaskOrganizer. - && !inPinnedWindowingMode() && getParent() != null - && !getParent().inPinnedWindowingMode(); + if (mWmService.mDisableTransitionAnimation || !okToAnimate()) return false; + + // Change transition only make sense as we go from "visible" to "visible". + if (mDisplayContent == null || getSurfaceControl() == null + || !isVisible() || !isVisibleRequested()) { + return false; + } + + // Make sure display isn't a part of the transition already - needed for legacy transitions. + if (mDisplayContent.inTransition()) return false; + + if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) { + // Screenshots are turned off when PiP is undergoing changes. + return !inPinnedWindowingMode() && getParent() != null + && !getParent().inPinnedWindowingMode(); + } + return true; } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 08acd24ffe7c..a69a07f9aee0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1048,29 +1048,6 @@ public class WindowManagerService extends IWindowManager.Stub SystemPerformanceHinter mSystemPerformanceHinter; - void openSurfaceTransaction() { - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); - SurfaceControl.openTransaction(); - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - - /** - * Closes a surface transaction. - * @param where debug string indicating where the transaction originated - */ - void closeSurfaceTransaction(String where) { - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction"); - SurfaceControl.closeTransaction(); - mWindowTracing.logState(where); - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - /** Listener to notify activity manager about app transitions. */ final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier = new WindowManagerInternal.AppTransitionListener() { @@ -8591,39 +8568,6 @@ public class WindowManagerService extends IWindowManager.Stub mAppFreezeListeners.remove(listener); } - /** - * WARNING: This interrupts surface updates, be careful! Don't - * execute within the transaction for longer than you would - * execute on an animation thread. - * WARNING: This method contains locks known to the State of California - * to cause Deadlocks and other conditions. - * - * Begins a surface transaction with which the AM can batch operations. - * All Surface updates performed by the WindowManager following this - * will not appear on screen until after the call to - * closeSurfaceTransaction. - * - * ActivityManager can use this to ensure multiple 'commands' will all - * be reflected in a single frame. For example when reparenting a window - * which was previously hidden due to it's parent properties, we may - * need to ensure it is hidden in the same frame that the properties - * from the new parent are inherited, otherwise it could be revealed - * mistakenly. - * - * TODO(b/36393204): We can investigate totally replacing #deferSurfaceLayout - * with something like this but it seems that some existing cases of - * deferSurfaceLayout may be a little too broad, in particular the total - * enclosure of startActivityUnchecked which could run for quite some time. - */ - void inSurfaceTransaction(Runnable exec) { - SurfaceControl.openTransaction(); - try { - exec.run(); - } finally { - SurfaceControl.closeTransaction(); - } - } - /** Called to inform window manager if non-Vr UI shoul be disabled or not. */ public void disableNonVrUi(boolean disable) { synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index e9d803888e9a..558bf9d6310a 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1666,7 +1666,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private void scheduleClientTransactionItem(@NonNull IApplicationThread thread, @NonNull ClientTransactionItem transactionItem) { try { - mAtm.getLifecycleManager().scheduleTransaction(thread, transactionItem); + mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem); } catch (Exception e) { Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem=" + transactionItem + " owner=" + mOwner, e); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5293292d74b9..3e43908994ad 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -29,7 +29,6 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerManager.DRAW_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.SurfaceControl.Transaction; -import static android.view.SurfaceControl.getGlobalTransaction; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -2794,7 +2793,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY); } if (!isVisibleByPolicy()) { - mWinAnimator.hide(getGlobalTransaction(), "checkPolicyVisibilityChange"); + mWinAnimator.hide(getPendingTransaction(), "checkPolicyVisibilityChange"); if (isFocused()) { ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setAnimationLocked: setting mFocusMayChange true"); diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 6c15c227a3b4..d348491b3d2a 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -20,7 +20,6 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.SurfaceControl.METADATA_OWNER_PID; import static android.view.SurfaceControl.METADATA_OWNER_UID; import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; -import static android.view.SurfaceControl.getGlobalTransaction; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; @@ -148,14 +147,9 @@ class WindowSurfaceController { if (mSurfaceControl == null) { return; } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked"); - mService.openSurfaceTransaction(); - try { - getGlobalTransaction().setOpaque(mSurfaceControl, isOpaque); - } finally { - mService.closeSurfaceTransaction("setOpaqueLocked"); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked"); - } + + mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque); + mService.scheduleAnimationLocked(); } void setSecure(boolean isSecure) { @@ -165,18 +159,15 @@ class WindowSurfaceController { return; } if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked"); - mService.openSurfaceTransaction(); - try { - getGlobalTransaction().setSecure(mSurfaceControl, isSecure); - final DisplayContent dc = mAnimator.mWin.mDisplayContent; - if (dc != null) { - dc.refreshImeSecureFlag(getGlobalTransaction()); - } - } finally { - mService.closeSurfaceTransaction("setSecure"); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked"); + final SurfaceControl.Transaction t = mAnimator.mWin.getPendingTransaction(); + t.setSecure(mSurfaceControl, isSecure); + + final DisplayContent dc = mAnimator.mWin.mDisplayContent; + if (dc != null) { + dc.refreshImeSecureFlag(t); } + mService.scheduleAnimationLocked(); } void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) { diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index ccd9bd0a50ca..7edf445d7604 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -20,7 +20,6 @@ #include <aidl/android/hardware/power/IPower.h> #include <android-base/stringprintf.h> -#include <inttypes.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <powermanager/PowerHalController.h> @@ -39,15 +38,6 @@ using android::base::StringPrintf; namespace android { -static struct { - jclass clazz{}; - jfieldID workPeriodStartTimestampNanos{}; - jfieldID actualTotalDurationNanos{}; - jfieldID actualCpuDurationNanos{}; - jfieldID actualGpuDurationNanos{}; - jfieldID timestampNanos{}; -} gWorkDurationInfo; - static power::PowerHalController gPowerHalController; static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap; static std::mutex gSessionMapLock; @@ -190,26 +180,6 @@ static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, ji setMode(session_ptr, static_cast<SessionMode>(mode), enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, - jobjectArray jWorkDurations) { - int size = env->GetArrayLength(jWorkDurations); - std::vector<WorkDuration> workDurations(size); - for (int i = 0; i < size; i++) { - jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); - workDurations[i].workPeriodStartTimestampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos); - workDurations[i].durationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos); - workDurations[i].cpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos); - workDurations[i].gpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos); - workDurations[i].timeStampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos); - } - reportActualWorkDuration(session_ptr, workDurations); -} - // ---------------------------------------------------------------------------- static const JNINativeMethod sHintManagerServiceMethods[] = { /* name, signature, funcPtr */ @@ -224,23 +194,9 @@ static const JNINativeMethod sHintManagerServiceMethods[] = { {"nativeSendHint", "(JI)V", (void*)nativeSendHint}, {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode}, - {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V", - (void*)nativeReportActualWorkDuration2}, }; int register_android_server_HintManagerService(JNIEnv* env) { - gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration"); - gWorkDurationInfo.workPeriodStartTimestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J"); - gWorkDurationInfo.actualTotalDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J"); - gWorkDurationInfo.actualCpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J"); - gWorkDurationInfo.actualGpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J"); - gWorkDurationInfo.timestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J"); - return jniRegisterNativeMethods(env, "com/android/server/power/hint/" "HintManagerService$NativeWrapper", diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 010604f9aaaa..02032c786563 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -1706,7 +1706,7 @@ class AppIdPermissionPolicy : SchemePolicy() { } /** Listener for permission flags changes. */ - abstract class OnPermissionFlagsChangedListener { + interface OnPermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. * @@ -1714,7 +1714,7 @@ class AppIdPermissionPolicy : SchemePolicy() { * and only call external code after [onStateMutated] when the new state has actually become * the current state visible to external code. */ - abstract fun onPermissionFlagsChanged( + fun onPermissionFlagsChanged( appId: Int, userId: Int, permissionName: String, @@ -1727,6 +1727,6 @@ class AppIdPermissionPolicy : SchemePolicy() { * * Implementations should keep this method fast to avoid stalling the locked state mutation. */ - abstract fun onStateMutated() + fun onStateMutated() } } diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 7db09f9125de..bb68bc5c791d 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -263,10 +263,6 @@ class DevicePermissionPolicy : SchemePolicy() { synchronized(listenersLock) { listeners = listeners + listener } } - fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { listeners = listeners - listener } - } - private fun isDeviceAwarePermission(permissionName: String): Boolean = DEVICE_AWARE_PERMISSIONS.contains(permissionName) @@ -283,11 +279,8 @@ class DevicePermissionPolicy : SchemePolicy() { } } - /** - * TODO: b/289355341 - implement listener for permission changes Listener for permission flags - * changes. - */ - abstract class OnDevicePermissionFlagsChangedListener { + /** Listener for permission flags changes. */ + interface OnDevicePermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. * @@ -295,7 +288,7 @@ class DevicePermissionPolicy : SchemePolicy() { * and only call external code after [onStateMutated] when the new state has actually become * the current state visible to external code. */ - abstract fun onDevicePermissionFlagsChanged( + fun onDevicePermissionFlagsChanged( appId: Int, userId: Int, deviceId: String, @@ -309,6 +302,6 @@ class DevicePermissionPolicy : SchemePolicy() { * * Implementations should keep this method fast to avoid stalling the locked state mutation. */ - abstract fun onStateMutated() + fun onStateMutated() } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index ab3d78c9958c..0d196b48b3f2 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -19,6 +19,7 @@ package com.android.server.permission.access.permission import android.Manifest import android.app.ActivityManager import android.app.AppOpsManager +import android.companion.virtual.VirtualDeviceManager import android.compat.annotation.ChangeId import android.compat.annotation.EnabledAfter import android.content.Context @@ -169,6 +170,7 @@ class PermissionService(private val service: AccessCheckingService) : onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper) onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener() policy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener) + devicePolicy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener) } override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> { @@ -2616,10 +2618,11 @@ class PermissionService(private val service: AccessCheckingService) : /** Callback invoked when interesting actions have been taken on a permission. */ private inner class OnPermissionFlagsChangedListener : - AppIdPermissionPolicy.OnPermissionFlagsChangedListener() { + AppIdPermissionPolicy.OnPermissionFlagsChangedListener, + DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener { private var isPermissionFlagsChanged = false - private val runtimePermissionChangedUids = MutableIntSet() + private val runtimePermissionChangedUidDevices = MutableIntMap<MutableSet<String>>() // Mapping from UID to whether only notifications permissions are revoked. private val runtimePermissionRevokedUids = SparseBooleanArray() private val gidsChangedUids = MutableIntSet() @@ -2642,6 +2645,24 @@ class PermissionService(private val service: AccessCheckingService) : oldFlags: Int, newFlags: Int ) { + onDevicePermissionFlagsChanged( + appId, + userId, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT, + permissionName, + oldFlags, + newFlags + ) + } + + override fun onDevicePermissionFlagsChanged( + appId: Int, + userId: Int, + deviceId: String, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) { isPermissionFlagsChanged = true val uid = UserHandle.getUid(userId, appId) @@ -2655,12 +2676,12 @@ class PermissionService(private val service: AccessCheckingService) : // permission flags have changed for a non-runtime permission, now we no longer do // that because permission flags are only for runtime permissions and the listeners // aren't being notified of non-runtime permission grant state changes anyway. - runtimePermissionChangedUids += uid if (wasPermissionGranted && !isPermissionGranted) { runtimePermissionRevokedUids[uid] = permissionName in NOTIFICATIONS_PERMISSIONS && runtimePermissionRevokedUids.get(uid, true) } + runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId } if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) { @@ -2674,10 +2695,12 @@ class PermissionService(private val service: AccessCheckingService) : isPermissionFlagsChanged = false } - runtimePermissionChangedUids.forEachIndexed { _, uid -> - onPermissionsChangeListeners.onPermissionsChanged(uid) + runtimePermissionChangedUidDevices.forEachIndexed { _, uid, deviceIds -> + deviceIds.forEach { deviceId -> + onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId) + } } - runtimePermissionChangedUids.clear() + runtimePermissionChangedUidDevices.clear() if (!isKillRuntimePermissionRevokedUidsSkipped) { val reason = @@ -2749,15 +2772,16 @@ class PermissionService(private val service: AccessCheckingService) : when (msg.what) { MSG_ON_PERMISSIONS_CHANGED -> { val uid = msg.arg1 - handleOnPermissionsChanged(uid) + val deviceId = msg.obj as String + handleOnPermissionsChanged(uid, deviceId) } } } - private fun handleOnPermissionsChanged(uid: Int) { + private fun handleOnPermissionsChanged(uid: Int, deviceId: String) { listeners.broadcast { listener -> try { - listener.onPermissionsChanged(uid) + listener.onPermissionsChanged(uid, deviceId) } catch (e: RemoteException) { Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) } @@ -2772,9 +2796,9 @@ class PermissionService(private val service: AccessCheckingService) : listeners.unregister(listener) } - fun onPermissionsChanged(uid: Int) { + fun onPermissionsChanged(uid: Int, deviceId: String) { if (listeners.registeredCallbackCount > 0) { - obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget() + obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget() } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml index 6c24d6d6e5a6..820628c98dee 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml @@ -21,8 +21,8 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> - <option name="test-file-name" value="FrameworksImeTests.apk" /> <option name="test-file-name" value="SimpleTestIme.apk" /> + <option name="test-file-name" value="FrameworksImeTests.apk" /> </target_preparer> <option name="test-tag" value="FrameworksImeTests" /> diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index b63a58a96b8c..21342783b79c 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import android.app.Instrumentation; import android.content.Context; @@ -48,6 +49,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper; import com.android.apps.inputmethod.simpleime.testing.TestActivity; +import com.android.internal.inputmethod.InputMethodNavButtonFlags; import org.junit.After; import org.junit.Before; @@ -635,6 +637,82 @@ public class InputMethodServiceTest { .getRootWindowInsets().getInsetsIgnoringVisibility(captionBar())); } + /** + * This checks that trying to show and hide the navigation bar takes effect + * when the IME does draw the IME navigation bar. + */ + @Test + public void testShowHideImeNavigationBar_doesDrawImeNavBar() throws Exception { + boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService() + .hasNavigationBar(mInputMethodService.getDisplayId()); + assumeTrue("Must have a navigation bar", hasNavigationBar); + + setShowImeWithHardKeyboard(true /* enabled */); + + // Show IME + verifyInputViewStatusOnMainSync( + () -> { + mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged( + InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR + | InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN + ); + assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(); + }, + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue(); + + // Try to hide IME nav bar + mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow() + .getInsetsController().hide(captionBar())); + mInstrumentation.waitForIdleSync(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); + + // Try to show IME nav bar + mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow() + .getInsetsController().show(captionBar())); + mInstrumentation.waitForIdleSync(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isTrue(); + } + /** + * This checks that trying to show and hide the navigation bar has no effect + * when the IME does not draw the IME navigation bar. + * + * Note: The IME navigation bar is *never* visible in 3 button navigation mode. + */ + @Test + public void testShowHideImeNavigationBar_doesNotDrawImeNavBar() throws Exception { + boolean hasNavigationBar = WindowManagerGlobal.getWindowManagerService() + .hasNavigationBar(mInputMethodService.getDisplayId()); + assumeTrue("Must have a navigation bar", hasNavigationBar); + + setShowImeWithHardKeyboard(true /* enabled */); + + // Show IME + verifyInputViewStatusOnMainSync(() -> { + mInputMethodService.getInputMethodInternal().onNavButtonFlagsChanged( + 0 /* navButtonFlags */); + assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(); + }, + true /* expected */, + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isTrue(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); + + // Try to hide IME nav bar + mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow() + .getInsetsController().hide(captionBar())); + mInstrumentation.waitForIdleSync(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); + + // Try to show IME nav bar + mInstrumentation.runOnMainSync(() -> mInputMethodService.getWindow().getWindow() + .getInsetsController().show(captionBar())); + mInstrumentation.waitForIdleSync(); + assertThat(mInputMethodService.isImeNavigationBarShownForTesting()).isFalse(); + } + private void verifyInputViewStatus( Runnable runnable, boolean expected, boolean inputViewStarted) throws InterruptedException { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java index a400f1243afb..eb6e8b4469f0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java @@ -44,6 +44,7 @@ public class DisplayBrightnessStateTest { float brightness = 0.3f; float sdrBrightness = 0.2f; boolean shouldUseAutoBrightness = true; + boolean shouldUpdateScreenBrightnessSetting = true; BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC); brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED); @@ -52,12 +53,15 @@ public class DisplayBrightnessStateTest { .setSdrBrightness(sdrBrightness) .setBrightnessReason(brightnessReason) .setShouldUseAutoBrightness(shouldUseAutoBrightness) + .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting) .build(); assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA); assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA); assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason); assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness); + assertEquals(shouldUpdateScreenBrightnessSetting, + displayBrightnessState.shouldUpdateScreenBrightnessSetting()); assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState)); } @@ -71,6 +75,7 @@ public class DisplayBrightnessStateTest { .setBrightness(0.26f) .setSdrBrightness(0.23f) .setShouldUseAutoBrightness(false) + .setShouldUpdateScreenBrightnessSetting(true) .build(); DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build(); assertEquals(state1, state2); @@ -92,7 +97,9 @@ public class DisplayBrightnessStateTest { .append("\n maxBrightness:") .append(displayBrightnessState.getMaxBrightness()) .append("\n customAnimationRate:") - .append(displayBrightnessState.getCustomAnimationRate()); + .append(displayBrightnessState.getCustomAnimationRate()) + .append("\n shouldUpdateScreenBrightnessSetting:") + .append(displayBrightnessState.shouldUpdateScreenBrightnessSetting()); return sb.toString(); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 0bf46547ce1e..353a7bb580ec 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -2754,8 +2754,7 @@ public class DisplayManagerServiceTest { DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class); localService.registerDisplayOffloader(displayId, mockDisplayOffloader); - assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo( - mockDisplayOffloader); + assertThat(display.getDisplayOffloadSessionLocked()).isNotNull(); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java new file mode 100644 index 000000000000..dea838d3763d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java @@ -0,0 +1,91 @@ +/* + * 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 com.android.server.display; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class DisplayOffloadSessionImplTest { + + @Mock + private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + + @Mock + private DisplayPowerControllerInterface mDisplayPowerController; + + private DisplayOffloadSessionImpl mSession; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mDisplayOffloader.startOffload()).thenReturn(true); + mSession = new DisplayOffloadSessionImpl(mDisplayOffloader, mDisplayPowerController); + } + + @Test + public void testStartOffload() { + mSession.startOffload(); + assertTrue(mSession.isActive()); + + // An active session shouldn't be started again + mSession.startOffload(); + verify(mDisplayOffloader, times(1)).startOffload(); + } + + @Test + public void testStopOffload() { + mSession.startOffload(); + mSession.stopOffload(); + + assertFalse(mSession.isActive()); + verify(mDisplayPowerController).setBrightnessFromOffload( + PowerManager.BRIGHTNESS_INVALID_FLOAT); + + // An inactive session shouldn't be stopped again + mSession.stopOffload(); + verify(mDisplayOffloader, times(1)).stopOffload(); + } + + @Test + public void testUpdateBrightness_sessionInactive() { + mSession.updateBrightness(0.3f); + verify(mDisplayPowerController, never()).setBrightnessFromOffload(anyFloat()); + } + + @Test + public void testUpdateBrightness_sessionActive() { + float brightness = 0.3f; + + mSession.startOffload(); + mSession.updateBrightness(brightness); + + verify(mDisplayPowerController).setBrightnessFromOffload(brightness); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 57f392a5887f..693cafefa2c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -127,8 +127,6 @@ public final class DisplayPowerController2Test { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; - private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @@ -148,6 +146,8 @@ public final class DisplayPowerController2Test { private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Mock private DisplayManagerFlags mDisplayManagerFlagsMock; + @Mock + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1160,19 +1160,19 @@ public final class DisplayPowerController2Test { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), - any(), + /* lightSensor= */ any(), eq(mHolder.brightnessMappingStrategy), - anyInt(), - anyFloat(), - anyFloat(), - anyFloat(), - anyInt(), - anyInt(), - anyLong(), - anyLong(), - anyLong(), - anyLong(), - anyBoolean(), + /* lightSensorWarmUpTime= */ anyInt(), + /* brightnessMin= */ anyFloat(), + /* brightnessMax= */ anyFloat(), + /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1180,9 +1180,9 @@ public final class DisplayPowerController2Test { eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), - isNull(), - anyInt(), - anyInt(), + /* idleModeBrightnessMapper= */ isNull(), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(brightness) ); @@ -1294,9 +1294,8 @@ public final class DisplayPowerController2Test { public void testRampRateForHdrContent_HdrClamperOn() { float clampedBrightness = 0.6f; float transitionRate = 1.5f; - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isHdrClamperEnabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isHdrClamperEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); DisplayPowerRequest dpr = new DisplayPowerRequest(); when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); @@ -1392,9 +1391,8 @@ public final class DisplayPowerController2Test { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1) public void testRampMaxTimeInteractiveThenIdle_DifferentValues() { - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isAdaptiveTone1Enabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); // Send a display power request DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1447,9 +1445,8 @@ public final class DisplayPowerController2Test { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1) public void testRampMaxTimeIdle_DifferentValues() { - DisplayManagerFlags flags = mock(DisplayManagerFlags.class); - when(flags.isAdaptiveTone1Enabled()).thenReturn(true); - mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, flags); + when(mDisplayManagerFlagsMock.isAdaptiveTone1Enabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true); // Send a display power request DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -1481,8 +1478,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1509,8 +1504,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1536,8 +1529,6 @@ public final class DisplayPowerController2Test { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with OFF. @@ -1553,26 +1544,29 @@ public final class DisplayPowerController2Test { verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); } - private void initDisplayOffloadSession() { - mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { - @Override - public boolean startOffload() { - return true; - } - - @Override - public void stopOffload() {} - }); - - mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; + @Test + public void testBrightnessFromOffload() { + when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + float brightness = 0.34f; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness( + any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + mHolder.dpc.setBrightnessFromOffload(brightness); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // One triggered by handleBrightnessModeChange, another triggered by + // setBrightnessFromOffload + verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false)); } /** @@ -1659,12 +1653,6 @@ public final class DisplayPowerController2Test { private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId, boolean isEnabled) { - return createDisplayPowerController(displayId, uniqueId, isEnabled, - mock(DisplayManagerFlags.class)); - } - - private DisplayPowerControllerHolder createDisplayPowerController(int displayId, - String uniqueId, boolean isEnabled, DisplayManagerFlags flags) { final DisplayPowerState displayPowerState = mock(DisplayPowerState.class); final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class); final AutomaticBrightnessController automaticBrightnessController = @@ -1690,7 +1678,7 @@ public final class DisplayPowerController2Test { TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper, - clamperController, flags)); + clamperController, mDisplayManagerFlagsMock)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -1705,7 +1693,7 @@ public final class DisplayPowerController2Test { mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> { }, - hbmMetadata, /* bootCompleted= */ false, flags); + hbmMetadata, /* bootCompleted= */ false, mDisplayManagerFlagsMock); return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 9617bd08fd93..b22799377872 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -125,8 +125,6 @@ public final class DisplayPowerControllerTest { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; - private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @@ -146,6 +144,8 @@ public final class DisplayPowerControllerTest { private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock; @Mock private DisplayManagerFlags mDisplayManagerFlagsMock; + @Mock + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; @@ -1094,19 +1094,19 @@ public final class DisplayPowerControllerTest { any(AutomaticBrightnessController.Callbacks.class), any(Looper.class), eq(mSensorManagerMock), - any(), + /* lightSensor= */ any(), eq(mHolder.brightnessMappingStrategy), - anyInt(), - anyFloat(), - anyFloat(), - anyFloat(), - anyInt(), - anyInt(), - anyLong(), - anyLong(), - anyLong(), - anyLong(), - anyBoolean(), + /* lightSensorWarmUpTime= */ anyInt(), + /* brightnessMin= */ anyFloat(), + /* brightnessMax= */ anyFloat(), + /* dozeScaleFactor */ anyFloat(), + /* lightSensorRate= */ anyInt(), + /* initialLightSensorRate= */ anyInt(), + /* brighteningLightDebounceConfig */ anyLong(), + /* darkeningLightDebounceConfig */ anyLong(), + /* brighteningLightDebounceConfigIdle= */ anyLong(), + /* darkeningLightDebounceConfigIdle= */ anyLong(), + /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1114,9 +1114,9 @@ public final class DisplayPowerControllerTest { eq(mContext), any(BrightnessRangeController.class), any(BrightnessThrottler.class), - isNull(), - anyInt(), - anyInt(), + /* idleModeBrightnessMapper= */ isNull(), + /* ambientLightHorizonShort= */ anyInt(), + /* ambientLightHorizonLong= */ anyInt(), eq(lux), eq(brightness) ); @@ -1386,8 +1386,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1414,8 +1412,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with DOZE. @@ -1441,8 +1437,6 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); return null; }).when(mHolder.displayPowerState).setScreenState(anyInt()); - // init displayoffload session and support offloading. - initDisplayOffloadSession(); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); // start with OFF. @@ -1458,28 +1452,6 @@ public final class DisplayPowerControllerTest { verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); } - private void initDisplayOffloadSession() { - mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { - @Override - public boolean startOffload() { - return true; - } - - @Override - public void stopOffload() {} - }); - - mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; - } - private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index a77a9586fb43..8270845657c6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; @@ -40,12 +41,12 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; -import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PowerManager; import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; @@ -118,10 +119,12 @@ public class LocalDisplayAdapterTest { private DisplayNotificationManager mMockedDisplayNotificationManager; @Mock private DisplayManagerFlags mFlags; + @Mock + private DisplayPowerControllerInterface mMockedDisplayPowerController; private Handler mHandler; - private DisplayOffloadSession mDisplayOffloadSession; + private DisplayOffloadSessionImpl mDisplayOffloadSession; private DisplayOffloader mDisplayOffloader; @@ -1195,6 +1198,7 @@ public class LocalDisplayAdapterTest { } verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload(); + assertTrue(mDisplayOffloadSession.isActive()); } @Test @@ -1217,6 +1221,9 @@ public class LocalDisplayAdapterTest { changeStateToDozeRunnable.run(); verify(mDisplayOffloader).stopOffload(); + assertFalse(mDisplayOffloadSession.isActive()); + verify(mMockedDisplayPowerController).setBrightnessFromOffload( + PowerManager.BRIGHTNESS_INVALID_FLOAT); } private void initDisplayOffloadSession() { @@ -1230,15 +1237,8 @@ public class LocalDisplayAdapterTest { public void stopOffload() {} }); - mDisplayOffloadSession = new DisplayOffloadSession() { - @Override - public void setDozeStateOverride(int displayState) {} - - @Override - public DisplayOffloader getDisplayOffloader() { - return mDisplayOffloader; - } - }; + mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader, + mMockedDisplayPowerController); } private void setupCutoutAndRoundedCorners() { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c4f483810478..52fa91f5fe0e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -42,7 +42,9 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.display.AutomaticBrightnessController; import com.android.server.display.BrightnessSetting; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.Before; import org.junit.Test; @@ -66,6 +68,8 @@ public final class DisplayBrightnessControllerTest { private BrightnessSetting mBrightnessSetting; @Mock private Runnable mOnBrightnessChangeRunnable; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; @Mock private HandlerExecutor mBrightnessChangeExecutor; @@ -74,7 +78,7 @@ public final class DisplayBrightnessControllerTest { DisplayBrightnessController.Injector() { @Override DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector( - Context context, int displayId) { + Context context, int displayId, DisplayManagerFlags flags) { return mDisplayBrightnessStrategySelector; } }; @@ -92,7 +96,7 @@ public final class DisplayBrightnessControllerTest { .thenReturn(true); mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector, DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable, - mBrightnessChangeExecutor); + mBrightnessChangeExecutor, mDisplayManagerFlags); } @Test @@ -350,7 +354,7 @@ public final class DisplayBrightnessControllerTest { int nonDefaultDisplayId = 1; mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector, nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting, - mOnBrightnessChangeRunnable, mBrightnessChangeExecutor); + mOnBrightnessChangeRunnable, mBrightnessChangeExecutor, mDisplayManagerFlags); brightness = 0.5f; when(mBrightnessSetting.getBrightness()).thenReturn(brightness); mDisplayBrightnessController.setAutomaticBrightnessController( @@ -384,4 +388,14 @@ public final class DisplayBrightnessControllerTest { verify(mBrightnessSetting).setBrightness(brightnessValue2); verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2); } + + @Test + public void setBrightnessFromOffload() { + float brightness = 0.4f; + OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn( + offloadBrightnessStrategy); + mDisplayBrightnessController.setBrightnessFromOffload(brightness); + verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index 8497dabba67d..37958faed1ca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -17,6 +17,7 @@ package com.android.server.display.brightness; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -35,13 +36,16 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy; import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.Before; import org.junit.Rule; @@ -71,10 +75,64 @@ public final class DisplayBrightnessStrategySelectorTest { @Mock private FollowerBrightnessStrategy mFollowerBrightnessStrategy; @Mock + private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy; + @Mock + private OffloadBrightnessStrategy mOffloadBrightnessStrategy; + @Mock private Resources mResources; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; private Context mContext; + private DisplayBrightnessStrategySelector.Injector mInjector = + new DisplayBrightnessStrategySelector.Injector() { + @Override + ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { + return mScreenOffBrightnessModeStrategy; + } + + @Override + DozeBrightnessStrategy getDozeBrightnessStrategy() { + return mDozeBrightnessModeStrategy; + } + + @Override + OverrideBrightnessStrategy getOverrideBrightnessStrategy() { + return mOverrideBrightnessStrategy; + } + + @Override + TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() { + return mTemporaryBrightnessStrategy; + } + + @Override + BoostBrightnessStrategy getBoostBrightnessStrategy() { + return mBoostBrightnessStrategy; + } + + @Override + FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) { + return mFollowerBrightnessStrategy; + } + + @Override + InvalidBrightnessStrategy getInvalidBrightnessStrategy() { + return mInvalidBrightnessStrategy; + } + + @Override + AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, + int displayId) { + return mAutomaticBrightnessStrategy; + } + + @Override + OffloadBrightnessStrategy getOffloadBrightnessStrategy() { + return mOffloadBrightnessStrategy; + } + }; @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @@ -87,45 +145,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mContext.getContentResolver()).thenReturn(contentResolver); when(mContext.getResources()).thenReturn(mResources); when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy"); - DisplayBrightnessStrategySelector.Injector injector = - new DisplayBrightnessStrategySelector.Injector() { - @Override - ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { - return mScreenOffBrightnessModeStrategy; - } - - @Override - DozeBrightnessStrategy getDozeBrightnessStrategy() { - return mDozeBrightnessModeStrategy; - } - - @Override - OverrideBrightnessStrategy getOverrideBrightnessStrategy() { - return mOverrideBrightnessStrategy; - } - - @Override - TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() { - return mTemporaryBrightnessStrategy; - } - - @Override - BoostBrightnessStrategy getBoostBrightnessStrategy() { - return mBoostBrightnessStrategy; - } - - @Override - FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) { - return mFollowerBrightnessStrategy; - } - - @Override - InvalidBrightnessStrategy getInvalidBrightnessStrategy() { - return mInvalidBrightnessStrategy; - } - }; mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, - injector, DISPLAY_ID); + mInjector, DISPLAY_ID, mDisplayManagerFlags); } @@ -188,6 +209,7 @@ public final class DisplayBrightnessStrategySelectorTest { displayPowerRequest.screenBrightnessOverride = Float.NaN; when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON), mInvalidBrightnessStrategy); } @@ -200,4 +222,35 @@ public final class DisplayBrightnessStrategySelectorTest { assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON), mFollowerBrightnessStrategy); } + + @Test + public void selectStrategySelectsOffloadStrategyWhenValid() { + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); + assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy( + displayPowerRequest, Display.STATE_ON)); + } + + @Test + public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() { + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); + assertNotEquals(mOffloadBrightnessStrategy, + mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_ON)); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index b652576a75c8..78ec2ff31161 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -188,6 +188,29 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_BrightnessReasonIsOffload() { + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + int targetDisplayState = Display.STATE_ON; + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_OFFLOAD; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true); + mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController) + .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, + mBrightnessConfiguration, + lastUserSetBrightness, + userSetBrightnessChanged, 0.5f, + false, policy, true); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()); + assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()); + } + + @Test public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() { mAutomaticBrightnessStrategy.setUseAutoBrightness(true); int targetDisplayState = Display.STATE_DOZE; diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java new file mode 100644 index 000000000000..36719af10abb --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.server.display.brightness.strategy; + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class OffloadBrightnessStrategyTest { + + private OffloadBrightnessStrategy mOffloadBrightnessStrategy; + + @Before + public void before() { + mOffloadBrightnessStrategy = new OffloadBrightnessStrategy(); + } + + @Test + public void testUpdateBrightnessWhenOffloadBrightnessIsSet() { + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + float brightness = 0.2f; + mOffloadBrightnessStrategy.setOffloadScreenBrightness(brightness); + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_OFFLOAD); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(brightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(brightness) + .setDisplayBrightnessStrategyName(mOffloadBrightnessStrategy.getName()) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mOffloadBrightnessStrategy.updateBrightness(displayPowerRequest); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 10f8510c7c70..4958f1c1c214 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -44,6 +44,7 @@ import static com.android.server.job.controllers.ConnectivityController.CcConfig import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_AVOID; import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_PREFER; import static com.android.server.job.controllers.ConnectivityController.TRANSPORT_AFFINITY_UNDEFINED; +import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,6 +52,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -989,6 +991,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = new ConnectivityController(mService, mFlexibilityController); + InOrder flexControllerInOrder = inOrder(mFlexibilityController); final Network meteredNet = mock(Network.class); final NetworkCapabilities meteredCaps = createCapabilitiesBuilder().build(); @@ -1042,10 +1045,13 @@ public class ConnectivityControllerTest { answerNetwork(generalCallback, redCallback, null, null, null); answerNetwork(generalCallback, blueCallback, null, null, null); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1059,13 +1065,15 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // No transport is specified. Accept the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1073,6 +1081,8 @@ public class ConnectivityControllerTest { // No transport is specified. Avoid the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1086,12 +1096,17 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // No transport is specified. Accept the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1099,6 +1114,8 @@ public class ConnectivityControllerTest { // No transport is specified. Avoid the network for transport affinity. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1112,8 +1129,13 @@ public class ConnectivityControllerTest { generalCallback.onLost(meteredNet); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // Only the metered network is lost. The unmetered network still satisfies the + // affinities. + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } // Specific UID was blocked @@ -1123,8 +1145,12 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // No change + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } // Metered wifi @@ -1134,10 +1160,13 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(meteredNet, meteredWifiCaps); - assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Wifi is preferred. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); @@ -1164,10 +1193,14 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCelullarCaps); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + // Metered network still has wifi transport + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Cellular is avoided. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); @@ -1185,6 +1218,14 @@ public class ConnectivityControllerTest { assertFalse(blue2.areTransportAffinitiesSatisfied()); } + // Remove wifi transport + { + generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps); + + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + } + // Undefined affinity final NetworkCapabilities unmeteredTestCaps = createCapabilitiesBuilder() .addCapability(NET_CAPABILITY_NOT_METERED) @@ -1198,14 +1239,16 @@ public class ConnectivityControllerTest { generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredTestCaps); - assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(blue2.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertTrue(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(red2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue2.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Undefined is preferred. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); assertTrue(red.areTransportAffinitiesSatisfied()); assertTrue(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1213,6 +1256,46 @@ public class ConnectivityControllerTest { // Undefined is avoided. setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + } + + // Lost all networks + { + // Set network as accepted to help confirm onLost notifies flex controller + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(true), anyLong()); + + answerNetwork(generalCallback, redCallback, unmeteredNet, null, null); + answerNetwork(generalCallback, blueCallback, unmeteredNet, null, null); + + generalCallback.onLost(meteredNet); + generalCallback.onLost(unmeteredNet); + + flexControllerInOrder.verify(mFlexibilityController) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), eq(false), anyLong()); + + assertFalse(red.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(blue.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, false); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); + assertFalse(red.areTransportAffinitiesSatisfied()); + assertFalse(blue.areTransportAffinitiesSatisfied()); + assertFalse(red2.areTransportAffinitiesSatisfied()); + assertFalse(blue2.areTransportAffinitiesSatisfied()); + // Undefined is avoided. + setDeviceConfigBoolean(controller, KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY, true); + controller.onConstantsUpdatedLocked(); + flexControllerInOrder.verify(mFlexibilityController, never()) + .setConstraintSatisfied(eq(CONSTRAINT_CONNECTIVITY), anyBoolean(), anyLong()); assertFalse(red.areTransportAffinitiesSatisfied()); assertFalse(blue.areTransportAffinitiesSatisfied()); assertFalse(red2.areTransportAffinitiesSatisfied()); @@ -1275,7 +1358,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = spy( new ConnectivityController(mService, mFlexibilityController)); doReturn(true).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); doReturn(true).when(controller).isNetworkAvailable(any()); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) @@ -1318,7 +1401,7 @@ public class ConnectivityControllerTest { final ConnectivityController controller = spy( new ConnectivityController(mService, mFlexibilityController)); doReturn(false).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); final JobStatus red = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED); @@ -1388,7 +1471,7 @@ public class ConnectivityControllerTest { // Both jobs would still be ready. Exception should not be revoked. doReturn(true).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); doReturn(true).when(controller).isNetworkAvailable(any()); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) @@ -1396,9 +1479,9 @@ public class ConnectivityControllerTest { // One job is still ready. Exception should not be revoked. doReturn(true).when(controller).wouldBeReadyWithConstraintLocked( - eq(redOne), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + eq(redOne), eq(CONSTRAINT_CONNECTIVITY)); doReturn(false).when(controller).wouldBeReadyWithConstraintLocked( - eq(redTwo), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + eq(redTwo), eq(CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, never()) .setAppIdleWhitelist(eq(UID_RED), anyBoolean()); @@ -1406,7 +1489,7 @@ public class ConnectivityControllerTest { // Both jobs are not ready. Exception should be revoked. doReturn(false).when(controller) - .wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY)); + .wouldBeReadyWithConstraintLocked(any(), eq(CONSTRAINT_CONNECTIVITY)); controller.reevaluateStateLocked(UID_RED); inOrder.verify(mNetPolicyManagerInternal, times(1)) .setAppIdleWhitelist(eq(UID_RED), eq(false)); @@ -1473,26 +1556,26 @@ public class ConnectivityControllerTest { controller.maybeStartTrackingJobLocked(unnetworked, null); answerNetwork(callback.getValue(), redCallback.getValue(), null, cellularNet, cellularCaps); - assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); networked.setStandbyBucket(RESTRICTED_INDEX); unnetworked.setStandbyBucket(RESTRICTED_INDEX); controller.startTrackingRestrictedJobLocked(networked); controller.startTrackingRestrictedJobLocked(unnetworked); - assertFalse(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a // connectivity constraint. - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); networked.setStandbyBucket(RARE_INDEX); unnetworked.setStandbyBucket(RARE_INDEX); controller.stopTrackingRestrictedJobLocked(networked); controller.stopTrackingRestrictedJobLocked(unnetworked); - assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertTrue(networked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); // Unnetworked shouldn't be affected by ConnectivityController since it doesn't have a // connectivity constraint. - assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); + assertFalse(unnetworked.isConstraintSatisfied(CONSTRAINT_CONNECTIVITY)); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index ee68b6d0e546..0659f7e9a064 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -19,19 +19,25 @@ package com.android.server.job.controllers; import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE; import static android.app.job.JobInfo.BIAS_TOP_APP; import static android.app.job.JobInfo.NETWORK_TYPE_ANY; +import static android.app.job.JobInfo.NETWORK_TYPE_CELLULAR; +import static android.app.job.JobInfo.NETWORK_TYPE_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS; +import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; +import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS; @@ -54,6 +60,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.net.NetworkRequest; import android.os.Looper; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -693,15 +700,59 @@ public class FlexibilityControllerTest { @Test public void testTransportAffinity() { - JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY); - JobStatus js = createJobStatus("testTopAppBypass", jb); + JobStatus jsAny = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + JobStatus jsCell = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_CELLULAR)); + JobStatus jsWifi = createJobStatus("testTransportAffinity", + createJob(0).setRequiredNetwork( + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build())); + // Disable the unseen constraint logic. + mFlexibilityController.setConstraintSatisfied( + SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, true, FROZEN_TIME); + mFlexibilityController.setConstraintSatisfied( + SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME); + // Require only a single constraint + jsAny.adjustNumRequiredFlexibleConstraints(-3); + jsCell.adjustNumRequiredFlexibleConstraints(-2); + jsWifi.adjustNumRequiredFlexibleConstraints(-2); synchronized (mFlexibilityController.mLock) { - js.setTransportAffinitiesSatisfied(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setTransportAffinitiesSatisfied(true); - assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); - js.setTransportAffinitiesSatisfied(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + jsAny.setTransportAffinitiesSatisfied(false); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, FROZEN_TIME); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // A good network exists, but the network hasn't been assigned to any of the jobs + jsAny.setTransportAffinitiesSatisfied(false); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, FROZEN_TIME); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // The good network has been assigned to the relevant jobs + jsAny.setTransportAffinitiesSatisfied(true); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(true); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); + + // One job loses access to the network. + jsAny.setTransportAffinitiesSatisfied(true); + jsCell.setTransportAffinitiesSatisfied(false); + jsWifi.setTransportAffinitiesSatisfied(false); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi)); } } @@ -768,6 +819,131 @@ public class FlexibilityControllerTest { } @Test + public void testHasEnoughSatisfiedConstraints_unseenConstraints_soonAfterBoot() { + // Add connectivity to require 4 constraints + JobStatus js = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + + // Too soon after boot + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(100 - 1), ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS - 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + + // Long after boot + + // No constraints ever seen. Don't bother waiting + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js)); + } + } + + @Test + public void testHasEnoughSatisfiedConstraints_unseenConstraints_longAfterBoot() { + // Add connectivity to require 4 constraints + JobStatus connJs = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + JobStatus nonConnJs = createJobStatus("testHasEnoughSatisfiedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_NONE)); + + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_BATTERY_NOT_LOW, true, + 2 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CHARGING, true, + 3 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_IDLE, true, + 4 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, + 5 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + + // Long after boot + // All constraints satisfied right now + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // Go down to 2 satisfied + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, + 6 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_IDLE, false, + 7 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + // 3 & 4 constraints were seen recently enough, so the job should wait + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 4 constraints still in the grace period. Wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli(16 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 3 constraints still in the grace period. Wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli(17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // 3 constraints haven't been seen recently. Don't wait. + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli( + 17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // Add then remove connectivity. Resets expectation of 3 constraints for connectivity jobs. + // Connectivity job should wait while the non-connectivity job can run. + // of getting back to 4 constraints. + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, + 18 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, false, + 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10); + JobSchedulerService.sElapsedRealtimeClock = + Clock.fixed( + Instant.ofEpochMilli( + 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1), + ZoneOffset.UTC); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + } + + @Test public void testResetJobNumDroppedConstraints() { JobInfo.Builder jb = createJob(22); JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 1e65c89643fd..a9f5b14fc48d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -182,6 +182,10 @@ public class PackageArchiverTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn( mock(Resources.class)); + when(mInstallerService.createSessionInternal(any(), any(), any(), anyInt(), + anyInt())).thenReturn(1); + when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn( + PackageInstaller.SessionInfo.INVALID_ID); doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class)))) .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(), eq(mUserId)); @@ -475,6 +479,7 @@ public class PackageArchiverTest { /* initialExtras= */ isNull()); Intent intent = intentCaptor.getValue(); assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0); + assertThat(intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, -1)).isEqualTo(1); assertThat(intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo( PACKAGE); assertThat( diff --git a/services/tests/mockingservicestests/src/com/android/server/power/OWNERS b/services/tests/mockingservicestests/src/com/android/server/power/OWNERS index fb62520ff57b..37396f392551 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/power/OWNERS @@ -1,3 +1,3 @@ include /services/core/java/com/android/server/power/OWNERS -per-file ThermalManagerServiceMockingTest.java=wvw@google.com,xwxw@google.com +per-file ThermalManagerServiceMockingTest.java=file:/THERMAL_OWNERS diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index 2d760a7dabac..1002fba3d60d 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -220,7 +220,7 @@ public class PinnerServiceTest { private void setDeviceConfigPinnedAnonSize(long size) { mFakeDeviceConfigInterface.setProperty( - DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, + DeviceConfig.NAMESPACE_RUNTIME_NATIVE, "pin_shared_anon_size", String.valueOf(size), /*makeDefault=*/false); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index a2e7cf3f1bb2..fd2cf6d4bb5f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -55,6 +55,10 @@ import android.os.Message; import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.TestableContext; import android.util.DebugUtils; @@ -69,6 +73,7 @@ import com.android.internal.util.ConcurrentUtils; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; @@ -134,6 +139,9 @@ import java.util.function.IntConsumer; @RunWith(AndroidJUnit4.class) public class FullScreenMagnificationGestureHandlerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public static final int STATE_IDLE = 1; public static final int STATE_ACTIVATED = 2; public static final int STATE_SHORTCUT_TRIGGERED = 3; @@ -425,6 +433,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testDisablingTripleTap_removesInputLag() { mMgh = newInstance(/* detectSingleFingerTripleTap */ false, /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true); @@ -436,6 +445,18 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() { + mMgh = newInstance(/* detectSingleFingerTripleTap */ false, + /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true); + goFromStateIdleTo(STATE_IDLE); + allowEventDelegation(); + tap(); + // no fast forward + verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt()); + } + + @Test public void testLongTapAfterShortcutTriggered_neverLogMagnificationTripleTap() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); @@ -510,6 +531,54 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + + assertIn(STATE_ACTIVATED); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() { + goFromStateIdleTo(STATE_ACTIVATED); + + twoFingerTap(); + twoFingerTap(); + twoFingerTap(); + + assertIn(STATE_IDLE); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerTapAndHold(); + + assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { + goFromStateIdleTo(STATE_IDLE); + + twoFingerTap(); + twoFingerTap(); + twoFingerSwipeAndHold(); + + assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); + } + + @Test public void testMultiTap_outOfDistanceSlop_shouldInIdle() { // All delay motion events should be sent, if multi-tap with out of distance slop. // STATE_IDLE will check if tapCount() < 2. @@ -1258,6 +1327,30 @@ public class FullScreenMagnificationGestureHandlerTest { send(upEvent()); } + private void twoFingerTap() { + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y)); + send(upEvent()); + } + + private void twoFingerTapAndHold() { + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + fastForward(2000); + } + + private void twoFingerSwipeAndHold() { + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + pointer1.offset(sWipeMinDistance + 1, 0); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0)); + } + private void triggerShortcut() { mMgh.notifyShortcutTriggered(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 0f3daec263e0..74eb79d7554c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -28,6 +28,8 @@ import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUT import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -248,6 +250,45 @@ public class AuthSessionTest { } @Test + public void testOnErrorReceivedBeforeOnDialogAnimatedIn() throws RemoteException { + final int fingerprintId = 0; + final int faceId = 1; + setupFingerprint(fingerprintId, FingerprintSensorProperties.TYPE_REAR); + setupFace(faceId, true /* confirmationAlwaysRequired */, + mock(IBiometricAuthenticator.class)); + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + 0 /* operationId */, + 0 /* userId */); + session.goToInitialState(); + + for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { + assertThat(sensor.getSensorState()).isEqualTo(BiometricSensor.STATE_WAITING_FOR_COOKIE); + session.onCookieReceived( + session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie()); + } + assertThat(session.allCookiesReceived()).isTrue(); + assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED); + + final BiometricSensor faceSensor = session.mPreAuthInfo.eligibleSensors.get(faceId); + final BiometricSensor fingerprintSensor = session.mPreAuthInfo.eligibleSensors.get( + fingerprintId); + final int cookie = faceSensor.getCookie(); + session.onErrorReceived(0, cookie, BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL, 0); + + assertThat(faceSensor.getSensorState()).isEqualTo(BiometricSensor.STATE_STOPPED); + assertThat(session.getState()).isEqualTo(STATE_ERROR_PENDING_SYSUI); + + session.onDialogAnimatedIn(true); + + assertThat(session.getState()).isEqualTo(STATE_AUTH_STARTED_UI_SHOWING); + assertThat(fingerprintSensor.getSensorState()).isEqualTo( + BiometricSensor.STATE_AUTHENTICATING); + } + + @Test public void testCancelReducesAppetiteForCookies() throws Exception { setupFace(0 /* id */, false /* confirmationAlwaysRequired */, mock(IBiometricAuthenticator.class)); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index 7e6883bcec63..ccbbaa52ac21 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -100,7 +100,7 @@ public class InputControllerTest { } @Test - public void registerInputDevice_deviceCreation_hasDeviceId() { + public void registerInputDevice_deviceCreation_hasDeviceId() throws Exception { final IBinder device1Token = new Binder("device1"); mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token, /* displayId= */ 1); @@ -124,7 +124,7 @@ public class InputControllerTest { } @Test - public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() { + public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() throws Exception { final IBinder deviceToken = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); @@ -137,7 +137,8 @@ public class InputControllerTest { } @Test - public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() { + public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() + throws Exception { final IBinder deviceToken = new Binder(); mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); @@ -153,7 +154,7 @@ public class InputControllerTest { } @Test - public void createNavigationTouchpad_hasDeviceId() { + public void createNavigationTouchpad_hasDeviceId() throws Exception { final IBinder deviceToken = new Binder(); mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -166,7 +167,7 @@ public class InputControllerTest { } @Test - public void createNavigationTouchpad_setsTypeAssociation() { + public void createNavigationTouchpad_setsTypeAssociation() throws Exception { final IBinder deviceToken = new Binder(); mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -176,7 +177,7 @@ public class InputControllerTest { } @Test - public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() { + public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() throws Exception { final IBinder deviceToken = new Binder(); mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -188,7 +189,7 @@ public class InputControllerTest { } @Test - public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() { + public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() throws Exception { final IBinder deviceToken = new Binder("device"); mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, deviceToken, @@ -201,56 +202,7 @@ public class InputControllerTest { } @Test - public void createInputDevice_tooLongNameRaisesException() { - final IBinder deviceToken = new Binder("device"); - // The underlying uinput implementation only supports device names up to 80 bytes. This - // string is all ASCII characters, therefore if we have more than 80 ASCII characters we - // will have more than 80 bytes. - String deviceName = - "This.is.a.very.long.device.name.that.exceeds.the.maximum.length.of.80.bytes" - + ".by.a.couple.bytes"; - - assertThrows(RuntimeException.class, () -> { - mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken, - 1); - }); - } - - @Test - public void createInputDevice_tooLongDeviceNameRaisesException() { - final IBinder deviceToken = new Binder("device"); - // The underlying uinput implementation only supports device names up to 80 bytes (including - // a 0-byte terminator). - // This string is 79 characters and 80 bytes (including the 0-byte terminator) - String deviceName = - "This.is.a.very.long.device.name.that.exceeds.the.maximum.length01234567890123456"; - - assertThrows(RuntimeException.class, () -> { - mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken, - 1); - }); - } - - @Test - public void createInputDevice_stringWithLessThanMaxCharsButMoreThanMaxBytesRaisesException() { - final IBinder deviceToken = new Binder("device1"); - - // Has only 39 characters but is 109 bytes as utf-8 - String device_name = - "░▄▄▄▄░\n" + - "▀▀▄██►\n" + - "▀▀███►\n" + - "░▀███►░█►\n" + - "▒▄████▀▀"; - - assertThrows(RuntimeException.class, () -> { - mInputController.createDpad(device_name, /*vendorId= */5, /*productId=*/5, - deviceToken, 1); - }); - } - - @Test - public void createInputDevice_duplicateNamesAreNotAllowed() { + public void createInputDevice_duplicateNamesAreNotAllowed() throws Exception { final IBinder deviceToken1 = new Binder("deviceToken1"); final IBinder deviceToken2 = new Binder("deviceToken2"); @@ -258,9 +210,9 @@ public class InputControllerTest { mInputController.createDpad(sharedDeviceName, /*vendorId= */4, /*productId=*/4, deviceToken1, 1); - assertThrows("Device names need to be unique", RuntimeException.class, () -> { - mInputController.createDpad(sharedDeviceName, /*vendorId= */5, /*productId=*/5, - deviceToken2, 2); - }); + assertThrows("Device names need to be unique", + InputController.DeviceCreationException.class, + () -> mInputController.createDpad( + sharedDeviceName, /*vendorId= */5, /*productId=*/5, deviceToken2, 2)); } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 30300ec3ad2e..d87b8d1cb60e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -368,6 +369,18 @@ public class VirtualDeviceManagerServiceTest { new Handler(TestableLooper.get(this).getLooper())); when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mNativeWrapperMock.writeButtonEvent(anyLong(), anyInt(), anyInt(), anyLong())) + .thenReturn(true); + when(mNativeWrapperMock.writeRelativeEvent(anyLong(), anyFloat(), anyFloat(), anyLong())) + .thenReturn(true); + when(mNativeWrapperMock.writeScrollEvent(anyLong(), anyFloat(), anyFloat(), anyLong())) + .thenReturn(true); + when(mNativeWrapperMock.writeKeyEvent(anyLong(), anyInt(), anyInt(), anyLong())) + .thenReturn(true); + when(mNativeWrapperMock.writeTouchEvent(anyLong(), anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong())) + .thenReturn(true); + mInputManagerMockHelper = new InputManagerMockHelper( TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock); // Allow virtual devices to be created on the looper thread for testing. @@ -1183,12 +1196,12 @@ public class VirtualDeviceManagerServiceTest { @Test public void sendKeyEvent_noFd() { - assertThrows( - IllegalArgumentException.class, - () -> - mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() - .setKeyCode(KeyEvent.KEYCODE_A) - .setAction(VirtualKeyEvent.ACTION_DOWN).build())); + assertThat(mDeviceImpl.sendKeyEvent(BINDER, + new VirtualKeyEvent.Builder() + .setKeyCode(KeyEvent.KEYCODE_A) + .setAction(VirtualKeyEvent.ACTION_DOWN) + .build())) + .isFalse(); } @Test @@ -1201,24 +1214,24 @@ public class VirtualDeviceManagerServiceTest { InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() - .setKeyCode(keyCode) - .setAction(action) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendKeyEvent(BINDER, + new VirtualKeyEvent.Builder() + .setKeyCode(keyCode) + .setAction(action) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action, eventTimeNanos); } @Test public void sendButtonEvent_noFd() { - assertThrows( - IllegalArgumentException.class, - () -> - mDeviceImpl.sendButtonEvent(BINDER, - new VirtualMouseButtonEvent.Builder() - .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK) - .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) - .build())); + assertThat(mDeviceImpl.sendButtonEvent(BINDER, + new VirtualMouseButtonEvent.Builder() + .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK) + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) + .build())) + .isFalse(); } @Test @@ -1231,38 +1244,24 @@ public class VirtualDeviceManagerServiceTest { InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); - mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() - .setButtonCode(buttonCode) - .setAction(action) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendButtonEvent(BINDER, + new VirtualMouseButtonEvent.Builder() + .setButtonCode(buttonCode) + .setAction(action) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action, eventTimeNanos); } @Test - public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() { - final int fd = 1; - final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; - final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.addDeviceForTesting(BINDER, fd, - InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, - INPUT_DEVICE_ID); - assertThrows( - IllegalStateException.class, - () -> - mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() - .setButtonCode(buttonCode) - .setAction(action).build())); - } - - @Test public void sendRelativeEvent_noFd() { - assertThrows( - IllegalArgumentException.class, - () -> - mDeviceImpl.sendRelativeEvent(BINDER, - new VirtualMouseRelativeEvent.Builder().setRelativeX( - 0.0f).setRelativeY(0.0f).build())); + assertThat(mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder() + .setRelativeX(0.0f) + .setRelativeY(0.0f) + .build())) + .isFalse(); } @Test @@ -1275,39 +1274,25 @@ public class VirtualDeviceManagerServiceTest { InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); - mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() - .setRelativeX(x) - .setRelativeY(y) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder() + .setRelativeX(x) + .setRelativeY(y) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y, eventTimeNanos); } - @Test - public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() { - final int fd = 1; - final float x = -0.2f; - final float y = 0.7f; - mInputController.addDeviceForTesting(BINDER, fd, - InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, - INPUT_DEVICE_ID); - assertThrows( - IllegalStateException.class, - () -> - mDeviceImpl.sendRelativeEvent(BINDER, - new VirtualMouseRelativeEvent.Builder() - .setRelativeX(x).setRelativeY(y).build())); - } @Test public void sendScrollEvent_noFd() { - assertThrows( - IllegalArgumentException.class, - () -> - mDeviceImpl.sendScrollEvent(BINDER, - new VirtualMouseScrollEvent.Builder() - .setXAxisMovement(-1f) - .setYAxisMovement(1f).build())); + assertThat(mDeviceImpl.sendScrollEvent(BINDER, + new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(-1f) + .setYAxisMovement(1f) + .build())) + .isFalse(); } @Test @@ -1320,42 +1305,28 @@ public class VirtualDeviceManagerServiceTest { InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); - mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() - .setXAxisMovement(x) - .setYAxisMovement(y) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendScrollEvent(BINDER, + new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeScrollEvent(fd, x, y, eventTimeNanos); } - @Test - public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() { - final int fd = 1; - final float x = 0.5f; - final float y = 1f; - mInputController.addDeviceForTesting(BINDER, fd, - InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, - INPUT_DEVICE_ID); - assertThrows( - IllegalStateException.class, - () -> - mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() - .setXAxisMovement(x) - .setYAxisMovement(y).build())); - } @Test public void sendTouchEvent_noFd() { - assertThrows( - IllegalArgumentException.class, - () -> - mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() - .setX(0.0f) - .setY(0.0f) - .setAction(VirtualTouchEvent.ACTION_UP) - .setPointerId(1) - .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) - .build())); + assertThat(mDeviceImpl.sendTouchEvent(BINDER, + new VirtualTouchEvent.Builder() + .setX(0.0f) + .setY(0.0f) + .setAction(VirtualTouchEvent.ACTION_UP) + .setPointerId(1) + .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) + .build())) + .isFalse(); } @Test @@ -1370,14 +1341,16 @@ public class VirtualDeviceManagerServiceTest { mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() - .setX(x) - .setY(y) - .setAction(action) - .setPointerId(pointerId) - .setToolType(toolType) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendTouchEvent(BINDER, + new VirtualTouchEvent.Builder() + .setX(x) + .setY(y) + .setAction(action) + .setPointerId(pointerId) + .setToolType(toolType) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, Float.NaN, eventTimeNanos); } @@ -1396,16 +1369,18 @@ public class VirtualDeviceManagerServiceTest { mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() - .setX(x) - .setY(y) - .setAction(action) - .setPointerId(pointerId) - .setToolType(toolType) - .setPressure(pressure) - .setMajorAxisSize(majorAxisSize) - .setEventTimeNanos(eventTimeNanos) - .build()); + assertThat(mDeviceImpl.sendTouchEvent(BINDER, + new VirtualTouchEvent.Builder() + .setX(x) + .setY(y) + .setAction(action) + .setPointerId(pointerId) + .setToolType(toolType) + .setPressure(pressure) + .setMajorAxisSize(majorAxisSize) + .setEventTimeNanos(eventTimeNanos) + .build())) + .isTrue(); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure, majorAxisSize, eventTimeNanos); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java index 258302354e45..01922e08d71d 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -57,12 +57,11 @@ public class VirtualCameraControllerTest { private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10; private static final int CAMERA_WIDTH_1 = 100; private static final int CAMERA_HEIGHT_1 = 200; - private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565; private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11; private static final int CAMERA_WIDTH_2 = 400; private static final int CAMERA_HEIGHT_2 = 600; - private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2; + private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888; @Mock private IVirtualCameraService mVirtualCameraServiceMock; @@ -81,7 +80,7 @@ public class VirtualCameraControllerTest { @Test public void registerCamera_registersCamera() throws Exception { mVirtualCameraController.registerCamera(createVirtualCameraConfig( - CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1)); ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = ArgumentCaptor.forClass(VirtualCameraConfiguration.class); @@ -89,13 +88,13 @@ public class VirtualCameraControllerTest { VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue(); assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1); assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1, - CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + CAMERA_HEIGHT_1, CAMERA_FORMAT); } @Test public void unregisterCamera_unregistersCamera() throws Exception { VirtualCameraConfig config = createVirtualCameraConfig( - CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1); + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1); mVirtualCameraController.unregisterCamera(config); verify(mVirtualCameraServiceMock).unregisterCamera(any()); @@ -104,9 +103,9 @@ public class VirtualCameraControllerTest { @Test public void close_unregistersAllCameras() throws Exception { mVirtualCameraController.registerCamera(createVirtualCameraConfig( - CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1)); mVirtualCameraController.registerCamera(createVirtualCameraConfig( - CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2)); + CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2)); ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = ArgumentCaptor.forClass(VirtualCameraConfiguration.class); @@ -117,9 +116,9 @@ public class VirtualCameraControllerTest { configurationCaptor.getAllValues(); assertThat(virtualCameraConfigurations).hasSize(2); assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1, - CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + CAMERA_HEIGHT_1, CAMERA_FORMAT); assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2, - CAMERA_HEIGHT_2, CAMERA_FORMAT_2); + CAMERA_HEIGHT_2, CAMERA_FORMAT); } private VirtualCameraConfig createVirtualCameraConfig( diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 2db46e60aea0..46ead854bded 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -571,6 +571,29 @@ public class JobStoreTest { } @Test + public void testDebugTagsPersisted() throws Exception { + JobInfo ji = new Builder(53, mComponent) + .setPersisted(true) + .addDebugTag("a") + .addDebugTag("b") + .addDebugTag("c") + .addDebugTag("d") + .removeDebugTag("d") + .build(); + final JobStatus js = JobStatus.createFromJobInfo(ji, SOME_UID, null, -1, null, null); + mTaskStoreUnderTest.add(js); + waitForPendingIo(); + + Set<String> expectedTags = Set.of("a", "b", "c"); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Debug tags not correctly persisted", + expectedTags, loaded.getJob().getDebugTags()); + } + + @Test public void testNamespacePersisted() throws Exception { final String namespace = "my.test.namespace"; JobInfo.Builder b = new Builder(93, mComponent) @@ -675,6 +698,22 @@ public class JobStoreTest { } @Test + public void testTraceTagPersisted() throws Exception { + JobInfo ji = new Builder(53, mComponent) + .setPersisted(true) + .setTraceTag("tag") + .build(); + final JobStatus js = JobStatus.createFromJobInfo(ji, SOME_UID, null, -1, null, null); + mTaskStoreUnderTest.add(js); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Trace tag not correctly persisted", "tag", loaded.getJob().getTraceTag()); + } + + @Test public void testEstimatedNetworkBytes() throws Exception { assertPersistedEquals(new JobInfo.Builder(0, mComponent) .setPersisted(true) diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java index 1e73a45a0c22..5aef7a320930 100644 --- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java @@ -90,7 +90,7 @@ public class AudioPoliciesDeviceRouteControllerTest { @Test public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() { - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); @@ -105,7 +105,7 @@ public class AudioPoliciesDeviceRouteControllerTest { audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; callAudioRoutesObserver(audioRoutesInfo); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -117,7 +117,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); } @@ -135,7 +135,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); } @@ -155,7 +155,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(null); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -171,7 +171,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); } @@ -202,7 +202,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.updateVolume(VOLUME_SAMPLE_1); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); } @@ -222,7 +222,7 @@ public class AudioPoliciesDeviceRouteControllerTest { mController.selectRoute(MediaRoute2Info.TYPE_DOCK); - MediaRoute2Info route2Info = mController.getDeviceRoute(); + MediaRoute2Info route2Info = mController.getSelectedRoute(); assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); } diff --git a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java index 24e48517f280..aed68a5dc7b5 100644 --- a/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java @@ -104,7 +104,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -129,7 +129,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -243,7 +243,7 @@ public class LegacyDeviceRouteControllerTest { mOnDeviceRouteChangedListener ); - MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = deviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType); assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue)) @@ -310,7 +310,7 @@ public class LegacyDeviceRouteControllerTest { // Simulating wired device being connected. callAudioRoutesObserver(audioRoutesInfo); - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME)) @@ -324,7 +324,7 @@ public class LegacyDeviceRouteControllerTest { AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); callAudioRoutesObserver(fakeBluetoothAudioRoute); - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) @@ -334,12 +334,12 @@ public class LegacyDeviceRouteControllerTest { @Test public void updateVolume_differentValue_updatesDeviceRouteVolume() { - MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue(); - actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + actualMediaRoute = mDeviceRouteController.getSelectedRoute(); assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1); } diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java index 949f8e7a6ab0..0e881efd4cdf 100644 --- a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java @@ -221,7 +221,7 @@ public class LockdownVpnTrackerTest { callCallbacksForNetworkConnect(defaultCallback, mNetwork); // Vpn is starting - verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP); + verify(mVpn).startLegacyVpnPrivileged(mProfile); verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), argThat(notification -> isExpectedNotification(notification, R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); @@ -242,7 +242,7 @@ public class LockdownVpnTrackerTest { // LockdownVpnTracker#handleStateChangedLocked. This is a bug. // TODO: consider fixing this. verify(mVpn, never()).stopVpnRunnerPrivileged(); - verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any()); + verify(mVpn, never()).startLegacyVpnPrivileged(any()); verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); } @@ -302,7 +302,7 @@ public class LockdownVpnTrackerTest { // Vpn is restarted. verify(mVpn).stopVpnRunnerPrivileged(); - verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp); + verify(mVpn).startLegacyVpnPrivileged(mProfile); verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), argThat(notification -> isExpectedNotification(notification, diff --git a/services/tests/servicestests/src/com/android/server/power/OWNERS b/services/tests/servicestests/src/com/android/server/power/OWNERS index ef4c0bf71cd7..fe93ebbf3d8c 100644 --- a/services/tests/servicestests/src/com/android/server/power/OWNERS +++ b/services/tests/servicestests/src/com/android/server/power/OWNERS @@ -1,3 +1,3 @@ include /services/core/java/com/android/server/power/OWNERS -per-file ThermalManagerServiceTest.java=wvw@google.com, xwxw@google.com
\ No newline at end of file +per-file ThermalManagerServiceTest.java=file:/THERMAL_OWNERS diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 37485275dac7..d09aa89179b8 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -44,7 +44,6 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; -import android.os.WorkDuration; import android.util.Log; import com.android.server.FgThread; @@ -90,11 +89,6 @@ public class HintManagerServiceTest { private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; - private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] { - new WorkDuration(1L, 11L, 8L, 4L, 1L), - new WorkDuration(2L, 13L, 8L, 6L, 2L), - new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L), - }; @Mock private Context mContext; @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @@ -599,55 +593,4 @@ public class HintManagerServiceTest { } a.close(); } - - @Test - public void testReportActualWorkDuration2() throws Exception { - HintManagerService service = createService(); - IBinder token = new Binder(); - - AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); - - a.updateTargetWorkDuration(100L); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), - eq(WORK_DURATIONS_THREE)); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2( - new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)}); - }); - - reset(mNativeWrapperMock); - // Set session to background, then the duration would not be updated. - service.mUidObserver.onUidStateChanged( - a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - - // Using CountDownLatch to ensure above onUidStateChanged() job was digested. - final CountDownLatch latch = new CountDownLatch(1); - FgThread.getHandler().post(() -> { - latch.countDown(); - }); - latch.await(); - - assertFalse(service.mUidObserver.isUidForeground(a.mUid)); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); - } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java index 8dcf89bb8438..999e33c24322 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -102,4 +102,18 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); assertThat(copy.shouldDisplayGrayscale()).isFalse(); } + + @Test + public void hasEffects_none_returnsFalse() { + ZenDeviceEffects effects = new ZenDeviceEffects.Builder().build(); + assertThat(effects.hasEffects()).isFalse(); + } + + @Test + public void hasEffects_some_returnsTrue() { + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .build(); + assertThat(effects.hasEffects()).isTrue(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 261b5d33b635..d4661075a552 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -33,6 +33,7 @@ import android.os.Parcel; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; import android.service.notification.ZenPolicy; @@ -327,7 +328,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.conditionId = CONDITION_ID; rule.condition = CONDITION; rule.enabled = ENABLED; - rule.creationTime = 123; rule.id = "id"; rule.zenMode = INTERRUPTION_FILTER; rule.modified = true; @@ -335,6 +335,18 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.snoozing = true; rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; + rule.zenDeviceEffects = new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(false) + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(false) + .setShouldUseNightMode(true) + .setShouldDisableAutoBrightness(false) + .setShouldDisableTapToWake(true) + .setShouldDisableTiltToWake(false) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(false) + .setShouldMaximizeDoze(true) + .build(); rule.creationTime = CREATION_TIME; rule.allowManualInvocation = ALLOW_MANUAL; @@ -362,6 +374,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.name, fromXml.name); assertEquals(rule.zenMode, fromXml.zenMode); assertEquals(rule.creationTime, fromXml.creationTime); + assertEquals(rule.zenPolicy, fromXml.zenPolicy); + assertEquals(rule.zenDeviceEffects, fromXml.zenDeviceEffects); assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation); assertEquals(rule.type, fromXml.type); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index fd3d5e9bf863..ed7e8ae5c6e5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -28,8 +30,10 @@ import android.content.ComponentName; import android.net.Uri; import android.provider.Settings; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeDiff; +import android.service.notification.ZenModeDiff.RuleDiff; import android.service.notification.ZenPolicy; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -42,10 +46,14 @@ import com.android.server.UiServiceTestCase; import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; @SmallTest @@ -56,6 +64,15 @@ public class ZenModeDiffTest extends UiServiceTestCase { public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS = Set.of("version", "manualRule", "automaticRules"); + // Differences for flagged fields are only generated if the flag is enabled. + // TODO: b/310620812 - Remove this exempt list when flag is inlined. + private static final Set<String> ZEN_RULE_EXEMPT_FIELDS = + android.app.Flags.modesApi() + ? Set.of() + : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, + RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, + RuleDiff.FIELD_ZEN_DEVICE_EFFECTS); + @Test public void testRuleDiff_addRemoveSame() { // Test add, remove, and both sides same @@ -86,7 +103,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); ArrayMap<String, Object> expectedTo = new ArrayMap<>(); List<Field> fieldsForDiff = getFieldsForDiffCheck( - ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule + ZenModeConfig.ZenRule.class, ZEN_RULE_EXEMPT_FIELDS); generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo); ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2); @@ -230,16 +247,21 @@ public class ZenModeDiffTest extends UiServiceTestCase { rule.name = "name"; rule.snoozing = true; rule.pkg = "a"; - rule.allowManualInvocation = true; - rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; - rule.iconResId = 123; - rule.triggerDescription = "At night"; + if (android.app.Flags.modesApi()) { + rule.allowManualInvocation = true; + rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; + rule.iconResId = 123; + rule.triggerDescription = "At night"; + rule.zenDeviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .build(); + } return rule; } // Get the fields on which we would want to check a diff. The requirements are: not final or/ // static (as these should/can never change), and not in a specific list that's exempted. - private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames) + private List<Field> getFieldsForDiffCheck(Class<?> c, Set<String> exemptNames) throws SecurityException { Field[] fields = c.getDeclaredFields(); ArrayList<Field> out = new ArrayList<>(); @@ -272,7 +294,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { f.setAccessible(true); // Just double-check also that the fields actually are for the class declared assertEquals(f.getDeclaringClass(), a.getClass()); - Class t = f.getType(); + Class<?> t = f.getType(); // handle the full set of primitive types first if (boolean.class.equals(t)) { f.setBoolean(a, true); @@ -305,8 +327,8 @@ public class ZenModeDiffTest extends UiServiceTestCase { f.set(a, null); expectedA.put(f.getName(), null); try { - f.set(b, t.getDeclaredConstructor().newInstance()); - expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance()); + f.set(b, newInstanceOf(t)); + expectedB.put(f.getName(), newInstanceOf(t)); } catch (Exception e) { // No default constructor, or blithely attempting to construct something doesn't // work for some reason. If the default value isn't null, then keep it. @@ -321,4 +343,34 @@ public class ZenModeDiffTest extends UiServiceTestCase { } } } + + private static Object newInstanceOf(Class<?> clazz) throws ReflectiveOperationException { + try { + Constructor<?> defaultConstructor = clazz.getDeclaredConstructor(); + return defaultConstructor.newInstance(); + } catch (Exception e) { + // No default constructor, continue below. + } + + // Look for a suitable builder. + Optional<Class<?>> clazzBuilder = + Arrays.stream(clazz.getDeclaredClasses()) + .filter(maybeBuilder -> maybeBuilder.getSimpleName().equals("Builder")) + .filter(maybeBuilder -> + Arrays.stream(maybeBuilder.getMethods()).anyMatch( + m -> m.getName().equals("build") + && m.getParameterCount() == 0 + && m.getReturnType().equals(clazz))) + .findFirst(); + if (clazzBuilder.isPresent()) { + Object builder = newInstanceOf(clazzBuilder.get()); + Method buildMethod = builder.getClass().getMethod("build"); + Object built = buildMethod.invoke(builder); + assertThat(built).isInstanceOf(clazz); + return built; + } + + throw new ReflectiveOperationException( + "Sorry! Couldn't figure out how to create an instance of " + clazz.getName()); + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index f2721a556454..7ea5010976ee 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -30,9 +30,9 @@ import static org.junit.Assert.assertTrue; import android.app.Instrumentation; import android.content.Context; -import android.os.Looper; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.Process; import android.os.SystemClock; import android.view.KeyEvent; @@ -109,7 +109,7 @@ public class SingleKeyGestureTests { } @Override - public void onPress(long downTime) { + public void onPress(long downTime, int displayId) { if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { return; } @@ -131,7 +131,7 @@ public class SingleKeyGestureTests { } @Override - void onMultiPress(long downTime, int count) { + void onMultiPress(long downTime, int count, int displayId) { if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { return; } @@ -141,7 +141,7 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount) { + void onKeyUp(long eventTime, int multiPressCount, int displayId) { mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); } }); @@ -159,7 +159,7 @@ public class SingleKeyGestureTests { } @Override - public void onPress(long downTime) { + public void onPress(long downTime, int displayId) { if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { return; } @@ -167,7 +167,7 @@ public class SingleKeyGestureTests { } @Override - void onMultiPress(long downTime, int count) { + void onMultiPress(long downTime, int count, int displayId) { if (mDetector.beganFromNonInteractive() && !mAllowNonInteractiveForPress) { return; } @@ -177,7 +177,7 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount) { + void onKeyUp(long eventTime, int multiPressCount, int displayId) { mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); } @@ -398,7 +398,7 @@ public class SingleKeyGestureTests { final SingleKeyGestureDetector.SingleKeyRule rule = new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER) { @Override - void onPress(long downTime) { + void onPress(long downTime, int displayId) { mShortPressed.countDown(); } }; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 1776ba556b72..786432a5dc88 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -490,7 +490,7 @@ public class ActivityRecordTests extends WindowTestsBase { ensureActivityConfiguration(activity); verify(mClientLifecycleManager, never()) - .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class)); + .scheduleTransactionItem(any(), isA(ActivityConfigurationChangeItem.class)); } @Test @@ -519,7 +519,7 @@ public class ActivityRecordTests extends WindowTestsBase { // The configuration change is still sent to the activity, even if it doesn't relaunch. final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mClientLifecycleManager).scheduleTransaction( + verify(mClientLifecycleManager).scheduleTransactionItem( eq(activity.app.getThread()), eq(expected)); } @@ -592,7 +592,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(expectedOrientation, currentConfig.orientation); final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, currentConfig); - verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); + verify(mClientLifecycleManager).scheduleTransactionItem(activity.app.getThread(), expected); verify(displayRotation).onSetRequestedOrientation(); } @@ -812,7 +812,8 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, activity.getConfiguration()); - verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); + verify(mClientLifecycleManager).scheduleTransactionItem( + activity.app.getThread(), expected); } finally { stack.getDisplayArea().removeChild(stack); } @@ -1785,7 +1786,7 @@ public class ActivityRecordTests extends WindowTestsBase { clearInvocations(mClientLifecycleManager); activity.getTask().removeImmediately("test"); try { - verify(mClientLifecycleManager).scheduleTransaction(any(), + verify(mClientLifecycleManager).scheduleTransactionItem(any(), isA(DestroyActivityItem.class)); } catch (RemoteException ignored) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e2bb115d5fbd..98055fa6f0d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -118,6 +118,7 @@ import com.android.compatibility.common.util.DeviceConfigStateHelper; import com.android.internal.util.FrameworkStatsLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; @@ -1378,7 +1379,8 @@ public class ActivityStarterTests extends WindowTestsBase { .setUserId(10) .build(); - final int result = starter.recycleTask(task, null, null, null); + final int result = starter.recycleTask(task, null, null, null, + BalVerdict.ALLOW_BY_DEFAULT); assertThat(result == START_SUCCESS).isTrue(); assertThat(starter.mAddingToTask).isTrue(); } @@ -1892,7 +1894,7 @@ public class ActivityStarterTests extends WindowTestsBase { starter.startActivityInner(target, source, null /* voiceSession */, null /* voiceInteractor */, 0 /* startFlags */, options, inTask, inTaskFragment, - BackgroundActivityStartController.BAL_ALLOW_DEFAULT, null /* intentGrants */, - -1 /* realCallingUid */); + BalVerdict.ALLOW_BY_DEFAULT, + null /* intentGrants */, -1 /* realCallingUid */); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 3c027ffa5dac..d2c731c3f8ad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -127,7 +127,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor = ArgumentCaptor.forClass(ClientTransactionItem.class); - verify(mockLifecycleManager).scheduleTransaction(any(), + verify(mockLifecycleManager).scheduleTransactionItem(any(), clientTransactionItemCaptor.capture()); final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue(); // Check that only an enter pip request item callback was scheduled. @@ -144,7 +144,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); + verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any()); } @Test @@ -156,7 +156,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); + verify(mClientLifecycleManager, never()).scheduleTransactionItem(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java index a18dbaf39575..04aa9815e698 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java @@ -16,18 +16,32 @@ package com.android.server.wm; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; + import android.app.IApplicationThread; +import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Build/Install/Run: @@ -37,23 +51,77 @@ import org.junit.Test; @Presubmit public class ClientLifecycleManagerTests { + @Mock + private IApplicationThread mClient; + @Mock + private IApplicationThread.Stub mNonBinderClient; + @Mock + private ClientTransactionItem mTransactionItem; + @Mock + private ActivityLifecycleItem mLifecycleItem; + @Captor + private ArgumentCaptor<ClientTransaction> mTransactionCaptor; + + private ClientLifecycleManager mLifecycleManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mLifecycleManager = spy(new ClientLifecycleManager()); + + doReturn(true).when(mLifecycleItem).isActivityLifecycleItem(); + } + @Test - public void testScheduleAndRecycleBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class))); + public void testScheduleTransaction_recycleBinderClientTransaction() throws Exception { + final ClientTransaction item = spy(ClientTransaction.obtain(mClient)); - ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); - clientLifecycleManager.scheduleTransaction(item); + mLifecycleManager.scheduleTransaction(item); - verify(item, times(1)).recycle(); + verify(item).recycle(); } @Test - public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class))); + public void testScheduleTransaction_notRecycleNonBinderClientTransaction() throws Exception { + final ClientTransaction item = spy(ClientTransaction.obtain(mNonBinderClient)); - ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); - clientLifecycleManager.scheduleTransaction(item); + mLifecycleManager.scheduleTransaction(item); + + verify(item, never()).recycle(); + } + + @Test + public void testScheduleTransactionItem() throws RemoteException { + doNothing().when(mLifecycleManager).scheduleTransaction(any()); + mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem); + + verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); + ClientTransaction transaction = mTransactionCaptor.getValue(); + assertEquals(1, transaction.getCallbacks().size()); + assertEquals(mTransactionItem, transaction.getCallbacks().get(0)); + assertNull(transaction.getLifecycleStateRequest()); + assertNull(transaction.getTransactionItems()); + + clearInvocations(mLifecycleManager); + mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem); + + verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); + transaction = mTransactionCaptor.getValue(); + assertNull(transaction.getCallbacks()); + assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest()); + } + + @Test + public void testScheduleTransactionAndLifecycleItems() throws RemoteException { + doNothing().when(mLifecycleManager).scheduleTransaction(any()); + mLifecycleManager.scheduleTransactionAndLifecycleItems(mClient, mTransactionItem, + mLifecycleItem); - verify(item, times(0)).recycle(); + verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); + final ClientTransaction transaction = mTransactionCaptor.getValue(); + assertEquals(1, transaction.getCallbacks().size()); + assertEquals(mTransactionItem, transaction.getCallbacks().get(0)); + assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 5b88c8cbec92..c6fa8a1b5a98 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -643,6 +643,21 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testDisplayHasContent() { + final WindowState window = createWindow(null, TYPE_APPLICATION_OVERLAY, "window"); + setDrawnState(WindowStateAnimator.COMMIT_DRAW_PENDING, window); + assertFalse(mDisplayContent.getLastHasContent()); + // The pending draw state should be committed and the has-content state is also updated. + mDisplayContent.applySurfaceChangesTransaction(); + assertTrue(window.isDrawn()); + assertTrue(mDisplayContent.getLastHasContent()); + // If the only window is no longer visible, has-content will be false. + setDrawnState(WindowStateAnimator.NO_SURFACE, window); + mDisplayContent.applySurfaceChangesTransaction(); + assertFalse(mDisplayContent.getLastHasContent()); + } + + @Test public void testImeIsAttachedToDisplayForLetterboxedApp() { final DisplayContent dc = mDisplayContent; final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window"); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 2af67452283b..e7ac33fc6a1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -43,12 +43,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.RefreshCallbackItem; import android.app.servertransaction.ResumeActivityItem; import android.content.ComponentName; @@ -529,8 +526,8 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); - when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat()) - .thenReturn(true); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); @@ -571,14 +568,14 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) .setIsRefreshAfterRotationRequested(true); - final ClientTransaction transaction = ClientTransaction.obtain(mActivity.app.getThread()); - transaction.addCallback(RefreshCallbackItem.obtain(mActivity.token, - cycleThroughStop ? ON_STOP : ON_PAUSE)); - transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(mActivity.token, - /* isForward */ false, /* shouldSendCompatFakeFocus */ false)); + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) - .scheduleTransaction(eq(transaction)); + .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(), + refreshCallbackItem, resumeActivityItem); } private void assertNoForceRotationOrRefresh() throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java index cf620fe605c6..c404c77c8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.server.wm.BackgroundActivityStartController.BalVerdict; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -186,7 +187,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options */null, /* inTask */null, /* inTaskFragment */ null, - /* balCode */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT, + BalVerdict.ALLOW_BY_DEFAULT, /* intentGrants */null, /* realCaiingUid */ -1); @@ -216,7 +217,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options= */null, /* inTask= */null, /* inTaskFragment= */ null, - /* balCode= */ BackgroundActivityStartController.BAL_ALLOW_DEFAULT, + BalVerdict.ALLOW_BY_DEFAULT, /* intentGrants= */null, /* realCaiingUid */ -1); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index d08ab51c9146..f99b489720b9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -273,7 +273,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { assertFalse(win.mHasSurface); assertNull(win.mWinAnimator.mSurfaceController); - doReturn(mSystemServicesTestRule.mTransaction).when(SurfaceControl::getGlobalTransaction); // Invisible requested activity should not get the last config even if its view is visible. mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0, outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 46cff8b82c6d..e152feb141e1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -306,7 +306,7 @@ public class WindowProcessControllerTests extends WindowTestsBase { @Test public void testCachedStateConfigurationChange() throws RemoteException { - doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any()); + doNothing().when(mClientLifecycleManager).scheduleTransactionItem(any(), any()); final IApplicationThread thread = mWpc.getThread(); final Configuration newConfig = new Configuration(mWpc.getConfiguration()); newConfig.densityDpi += 100; @@ -314,20 +314,20 @@ public class WindowProcessControllerTests extends WindowTestsBase { mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); clearInvocations(mClientLifecycleManager); mWpc.onConfigurationChanged(newConfig); - verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), any()); // Cached state won't send the change. clearInvocations(mClientLifecycleManager); mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); newConfig.densityDpi += 100; mWpc.onConfigurationChanged(newConfig); - verify(mClientLifecycleManager, never()).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any()); // Cached -> non-cached will send the previous deferred config immediately. mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER); final ArgumentCaptor<ConfigurationChangeItem> captor = ArgumentCaptor.forClass(ConfigurationChangeItem.class); - verify(mClientLifecycleManager).scheduleTransaction(eq(thread), captor.capture()); + verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), captor.capture()); final ClientTransactionHandler client = mock(ClientTransactionHandler.class); captor.getValue().preExecute(client); final ArgumentCaptor<Configuration> configCaptor = diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index a584fc9b2216..b77596391be1 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -2437,8 +2437,6 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (VoiceInteractionManagerServiceStub.this) { Slog.i(TAG, "Force stopping current voice recognizer: " + getCurRecognizer(userHandle)); - // TODO: Figure out why the interactor was being cleared and document it. - setCurInteractor(null, userHandle); initRecognizer(userHandle); } } diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java index f7c8237bdda4..cc8a992c45e2 100644 --- a/telephony/java/android/service/euicc/EuiccProfileInfo.java +++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java @@ -43,7 +43,11 @@ import java.util.Objects; @SystemApi public final class EuiccProfileInfo implements Parcelable { - /** Profile policy rules (bit mask) */ + /** + * Profile policy rules (bit mask) + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "POLICY_RULE_" }, value = { POLICY_RULE_DO_NOT_DISABLE, @@ -58,7 +62,11 @@ public final class EuiccProfileInfo implements Parcelable { /** This profile should be deleted after being disabled. */ public static final int POLICY_RULE_DELETE_AFTER_DISABLING = 1 << 2; - /** Class of the profile */ + /** + * Class of the profile + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "PROFILE_CLASS_" }, value = { PROFILE_CLASS_TESTING, @@ -79,7 +87,11 @@ public final class EuiccProfileInfo implements Parcelable { */ public static final int PROFILE_CLASS_UNSET = -1; - /** State of the profile */ + /** + * State of the profile + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "PROFILE_STATE_" }, value = { PROFILE_STATE_DISABLED, diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index 611f97b70c8c..e981e1f92071 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -67,7 +67,11 @@ import java.util.concurrent.Executor; public class EuiccCardManager { private static final String TAG = "EuiccCardManager"; - /** Reason for canceling a profile download session */ + /** + * Reason for canceling a profile download session + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"CANCEL_REASON_"}, value = { CANCEL_REASON_END_USER_REJECTED, @@ -97,7 +101,11 @@ public class EuiccCardManager { */ public static final int CANCEL_REASON_PPR_NOT_ALLOWED = 3; - /** Options for resetting eUICC memory */ + /** + * Options for resetting eUICC memory + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = {"RESET_OPTION_"}, value = { RESET_OPTION_DELETE_OPERATIONAL_PROFILES, diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index b9a7d439114a..86fbb04d31b6 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -552,9 +552,8 @@ public class EuiccManager { /** * Euicc OTA update status which can be got by {@link #getOtaStatus} - * @hide + * @removed mistakenly exposed as system-api previously */ - @SystemApi @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"EUICC_OTA_"}, value = { EUICC_OTA_IN_PROGRESS, diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java index be0048f73bf4..fcc0b6ae5653 100644 --- a/telephony/java/android/telephony/euicc/EuiccNotification.java +++ b/telephony/java/android/telephony/euicc/EuiccNotification.java @@ -36,7 +36,11 @@ import java.util.Objects; */ @SystemApi public final class EuiccNotification implements Parcelable { - /** Event */ + /** + * Event + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "EVENT_" }, value = { EVENT_INSTALL, diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java index 1c6b6b6e83fc..c35242d0bad8 100644 --- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java +++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java @@ -37,7 +37,11 @@ import java.util.List; */ @SystemApi public final class EuiccRulesAuthTable implements Parcelable { - /** Profile policy rule flags */ + /** + * Profile policy rule flags + * + * @removed mistakenly exposed previously + */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = { POLICY_RULE_FLAG_CONSENT_REQUIRED diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 93a558287bd6..c1784f3b42e7 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -149,7 +149,9 @@ class InputManagerServiceTests { verify(native).setMotionClassifierEnabled(anyBoolean()) verify(native).setMaximumObscuringOpacityForTouch(anyFloat()) verify(native).setStylusPointerIconEnabled(anyBoolean()) - verify(native).setKeyRepeatConfiguration(anyInt(), anyInt()) + // Called twice at boot, since there are individual callbacks to update the + // key repeat timeout and the key repeat delay. + verify(native, times(2)).setKeyRepeatConfiguration(anyInt(), anyInt()) } @Test diff --git a/tests/SmokeTestApps/Android.bp b/tests/SmokeTestApps/Android.bp index 3505fe1c4afb..38ee8ac99747 100644 --- a/tests/SmokeTestApps/Android.bp +++ b/tests/SmokeTestApps/Android.bp @@ -11,4 +11,7 @@ android_test { name: "SmokeTestTriggerApps", srcs: ["src/**/*.java"], sdk_version: "current", + errorprone: { + enabled: false, + }, } diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/tools/hoststubgen/hoststubgen/framework-policy-override.txt index ff0fe32ad267..493ad56a5cbb 100644 --- a/tools/hoststubgen/hoststubgen/framework-policy-override.txt +++ b/tools/hoststubgen/hoststubgen/framework-policy-override.txt @@ -78,6 +78,9 @@ class android.util.Log !com.android.hoststubgen.nativesubstitution.Log_host class com.android.internal.util.FastPrintWriter keepclass class com.android.internal.util.LineBreakBufferedWriter keepclass +class android.util.EventLog stubclass +class android.util.EventLog !com.android.hoststubgen.nativesubstitution.EventLog_host +class android.util.EventLog$Event stubclass # Expose Context because it's referred to by AndroidTestCase, but don't need to expose any of # its members. diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java new file mode 100644 index 000000000000..292e8da0de10 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java @@ -0,0 +1,69 @@ +/* + * 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 com.android.hoststubgen.nativesubstitution; + +import android.util.Log; +import android.util.Log.Level; + +import java.util.Collection; + +public class EventLog_host { + public static int writeEvent(int tag, int value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, long value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, float value) { + return writeEvent(tag, (Object) value); + } + + public static int writeEvent(int tag, String str) { + return writeEvent(tag, (Object) str); + } + + public static int writeEvent(int tag, Object... list) { + final StringBuilder sb = new StringBuilder(); + sb.append("logd: [event] "); + final String tagName = android.util.EventLog.getTagName(tag); + if (tagName != null) { + sb.append(tagName); + } else { + sb.append(tag); + } + sb.append(": ["); + for (int i = 0; i < list.length; i++) { + sb.append(String.valueOf(list[i])); + if (i < list.length - 1) { + sb.append(','); + } + } + sb.append(']'); + System.out.println(sb.toString()); + return sb.length(); + } + + public static void readEvents(int[] tags, Collection<android.util.EventLog.Event> output) { + throw new UnsupportedOperationException(); + } + + public static void readEventsOnWrapping(int[] tags, long timestamp, + Collection<android.util.EventLog.Event> output) { + throw new UnsupportedOperationException(); + } +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index 1bcf3642082f..d7aa0af13ce2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -119,8 +119,12 @@ fun getDirectOuterClassName(className: String): String? { * Write bytecode to push all the method arguments to the stack. * The number of arguments and their type are taken from [methodDescriptor]. */ -fun writeByteCodeToPushArguments(methodDescriptor: String, writer: MethodVisitor) { - var i = -1 +fun writeByteCodeToPushArguments( + methodDescriptor: String, + writer: MethodVisitor, + argOffset: Int = 0, + ) { + var i = argOffset - 1 Type.getArgumentTypes(methodDescriptor).forEach { type -> i++ @@ -159,6 +163,18 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) { } /** + * Given a method descriptor, insert an [argType] as the first argument to it. + */ +fun prependArgTypeToMethodDescriptor(methodDescriptor: String, argType: Type): String { + val returnType = Type.getReturnType(methodDescriptor) + val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList() + + argTypes.add(0, argType) + + return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray()) +} + +/** * Return the "visibility" modifier from an `access` integer. * * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index e63efd0e43ba..88db15b86143 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -19,6 +19,7 @@ import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate +import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor import com.android.hoststubgen.asm.writeByteCodeToPushArguments import com.android.hoststubgen.asm.writeByteCodeToReturn import com.android.hoststubgen.filters.FilterPolicy @@ -285,7 +286,7 @@ class ImplGeneratingAdapter( * class. */ private inner class NativeSubstitutingMethodAdapter( - access: Int, + val access: Int, private val name: String, private val descriptor: String, signature: String?, @@ -300,12 +301,33 @@ class ImplGeneratingAdapter( } override fun visitEnd() { - writeByteCodeToPushArguments(descriptor, this) + var targetDescriptor = descriptor + var argOffset = 0 + + // For non-static native method, we need to tweak it a bit. + if ((access and Opcodes.ACC_STATIC) == 0) { + // Push `this` as the first argument. + this.visitVarInsn(Opcodes.ALOAD, 0) + + // Update the descriptor -- add this class's type as the first argument + // to the method descriptor. + val thisType = Type.getType("L" + currentClassName + ";") + + targetDescriptor = prependArgTypeToMethodDescriptor( + descriptor, + thisType, + ) + + // Shift the original arguments by one. + argOffset = 1 + } + + writeByteCodeToPushArguments(descriptor, this, argOffset) visitMethodInsn(Opcodes.INVOKESTATIC, nativeSubstitutionClass, name, - descriptor, + targetDescriptor, false) writeByteCodeToReturn(descriptor, this) diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 3474ae49d3b6..673d3e8e25e9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -1718,7 +1718,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 2 + interfaces: 0, fields: 1, methods: 8, attributes: 2 + int value; + descriptor: I + flags: (0x0000) + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1767,6 +1771,40 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative Start Length Slot Name Signature 0 6 0 arg1 J 0 6 2 arg2 J + + public void setValue(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 0 6 1 v I + + public native int nativeNonStaticAddToValue(int); + descriptor: (I)I + flags: (0x0101) ACC_PUBLIC, ACC_NATIVE + + public int nativeNonStaticAddToValue_should_be_like_this(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 0 6 1 arg I } SourceFile: "TinyFrameworkNative.java" RuntimeInvisibleAnnotations: @@ -1782,9 +1820,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host minor version: 0 major version: 61 flags: (0x0021) ACC_PUBLIC, ACC_SUPER - this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 2 + interfaces: 0, fields: 0, methods: 4, attributes: 2 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1826,6 +1864,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 0 4 0 arg1 J 0 4 2 arg2 J + + public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: getfield #x // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I + x: iload_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 0 7 1 arg I } SourceFile: "TinyFrameworkNative_host.java" RuntimeInvisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index a1aae8a46c14..d12588ae4ce2 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -1039,7 +1039,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 1, methods: 8, attributes: 3 + int value; + descriptor: I + flags: (0x0000) + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1080,6 +1084,32 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow + + public void setValue(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public native int nativeNonStaticAddToValue(int); + descriptor: (I)I + flags: (0x0101) ACC_PUBLIC, ACC_NATIVE + + public int nativeNonStaticAddToValue_should_be_like_this(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 29626f26d367..97fb64f38b2d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -1650,7 +1650,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 1, methods: 8, attributes: 3 + int value; + descriptor: I + flags: (0x0000) + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1710,6 +1714,46 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative Start Length Slot Name Signature 0 6 0 arg1 J 0 6 2 arg2 J + + public void setValue(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 0 6 1 v I + + public int nativeNonStaticAddToValue(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ireturn + + public int nativeNonStaticAddToValue_should_be_like_this(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 0 6 1 arg I } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -1732,7 +1776,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 3, attributes: 3 + interfaces: 0, fields: 0, methods: 4, attributes: 3 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1792,6 +1836,28 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 15 4 0 arg1 J 15 4 2 arg2 J + + public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeNonStaticAddToValue + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: getfield #x // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I + x: iload_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 15 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 15 7 1 arg I } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index a1aae8a46c14..d12588ae4ce2 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -1039,7 +1039,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 1, methods: 8, attributes: 3 + int value; + descriptor: I + flags: (0x0000) + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -1080,6 +1084,32 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow + + public void setValue(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public native int nativeNonStaticAddToValue(int); + descriptor: (I)I + flags: (0x0101) ACC_PUBLIC, ACC_NATIVE + + public int nativeNonStaticAddToValue_should_be_like_this(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index ed7e7d3e6375..8035189b50c9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -2108,7 +2108,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 6, attributes: 3 + interfaces: 0, fields: 1, methods: 9, attributes: 3 + int value; + descriptor: I + flags: (0x0000) + private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC @@ -2193,6 +2197,56 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative Start Length Slot Name Signature 11 6 0 arg1 J 11 6 2 arg2 J + + public void setValue(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String setValue + x: ldc #x // String (I)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 11 6 1 v I + + public int nativeNonStaticAddToValue(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=2, locals=2, args_size=2 + x: aload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ireturn + + public int nativeNonStaticAddToValue_should_be_like_this(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String nativeNonStaticAddToValue_should_be_like_this + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 11 6 1 arg I } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -2215,7 +2269,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 4, attributes: 3 + interfaces: 0, fields: 0, methods: 5, attributes: 3 private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC @@ -2300,6 +2354,33 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 26 4 0 arg1 J 26 4 2 arg2 J + + public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeNonStaticAddToValue + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeNonStaticAddToValue + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: getfield #x // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I + x: iload_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + 26 7 1 arg I } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index c151dcc9c90e..e7b5d9fc2ece 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java @@ -32,4 +32,16 @@ public class TinyFrameworkNative { public static long nativeLongPlus_should_be_like_this(long arg1, long arg2) { return TinyFrameworkNative_host.nativeLongPlus(arg1, arg2); } + + int value; + + public void setValue(int v) { + this.value = v; + } + + public native int nativeNonStaticAddToValue(int arg); + + public int nativeNonStaticAddToValue_should_be_like_this(int arg) { + return TinyFrameworkNative_host.nativeNonStaticAddToValue(this, arg); + } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java index 48f7dea8c66c..749ebaa378e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java @@ -28,4 +28,10 @@ public class TinyFrameworkNative_host { public static long nativeLongPlus(long arg1, long arg2) { return arg1 + arg2; } + + // Note, the method must be static even for a non-static native method, but instead it + // must take the "source" instance as the first argument. + public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) { + return source.value + arg; + } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index b01566111250..d04ca52a5682 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -152,6 +152,13 @@ public class TinyFrameworkClassTest { } @Test + public void testNativeSubstitutionClass_nonStatic() { + TinyFrameworkNative instance = new TinyFrameworkNative(); + instance.setValue(5); + assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8); + } + + @Test public void testExitLog() { thrown.expect(RuntimeException.class); thrown.expectMessage("Outer exception"); |