diff options
103 files changed, 1602 insertions, 698 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 4f5eb37d871d..cbc9263a2c3d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1631,7 +1631,8 @@ public class JobSchedulerService extends com.android.server.SystemService jobStatus.getEstimatedNetworkDownloadBytes(), jobStatus.getEstimatedNetworkUploadBytes(), jobStatus.getWorkCount(), - ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid()))); + ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())), + jobStatus.getNamespaceHash()); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -2059,7 +2060,8 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.getEstimatedNetworkDownloadBytes(), cancelled.getEstimatedNetworkUploadBytes(), cancelled.getWorkCount(), - ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid()))); + ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())), + cancelled.getNamespaceHash()); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { 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 90f1523104ee..0b08b6faf971 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -499,7 +499,8 @@ public final class JobServiceContext implements ServiceConnection { job.getEstimatedNetworkDownloadBytes(), job.getEstimatedNetworkUploadBytes(), job.getWorkCount(), - ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid()))); + ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())), + job.getNamespaceHash()); sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { @@ -1557,7 +1558,8 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getEstimatedNetworkUploadBytes(), completedJob.getWorkCount(), ActivityManager - .processStateAmToProto(mService.getUidProcState(completedJob.getUid()))); + .processStateAmToProto(mService.getUidProcState(completedJob.getUid())), + completedJob.getNamespaceHash()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", getId()); 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 a8b4c695889c..edd531d13965 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 @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; import android.text.format.DateFormat; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -51,6 +52,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; @@ -65,10 +67,12 @@ import com.android.server.job.JobStatusShortInfoProto; import dalvik.annotation.optimization.NeverCompile; import java.io.PrintWriter; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Objects; +import java.util.Random; import java.util.function.Predicate; /** @@ -88,6 +92,13 @@ public final class JobStatus { private static final String TAG = "JobScheduler.JobStatus"; static final boolean DEBUG = JobSchedulerService.DEBUG; + private static MessageDigest sMessageDigest; + /** Cache of namespace to hash to reduce how often we need to generate the namespace hash. */ + @GuardedBy("sNamespaceHashCache") + private static final ArrayMap<String, String> sNamespaceHashCache = new ArrayMap<>(); + /** Maximum size of {@link #sNamespaceHashCache}. */ + private static final int MAX_NAMESPACE_CACHE_SIZE = 128; + private static final int NUM_CONSTRAINT_CHANGE_HISTORY = 10; public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; @@ -231,6 +242,8 @@ public final class JobStatus { final String sourceTag; @Nullable private final String mNamespace; + @Nullable + private final String mNamespaceHash; /** An ID that can be used to uniquely identify the job when logging statsd metrics. */ private final long mLoggingJobId; @@ -570,6 +583,7 @@ public final class JobStatus { this.callingUid = callingUid; this.standbyBucket = standbyBucket; mNamespace = namespace; + mNamespaceHash = generateNamespaceHash(namespace); mLoggingJobId = generateLoggingId(namespace, job.getId()); int tempSourceUid = -1; @@ -814,6 +828,56 @@ public final class JobStatus { return ((long) namespace.hashCode()) << 31 | jobId; } + @Nullable + private static String generateNamespaceHash(@Nullable String namespace) { + if (namespace == null) { + return null; + } + if (namespace.trim().isEmpty()) { + // Input is composed of all spaces (or nothing at all). + return namespace; + } + synchronized (sNamespaceHashCache) { + final int idx = sNamespaceHashCache.indexOfKey(namespace); + if (idx >= 0) { + return sNamespaceHashCache.valueAt(idx); + } + } + String hash = null; + try { + // .hashCode() can result in conflicts that would make distinguishing between + // namespaces hard and reduce the accuracy of certain metrics. Use SHA-256 + // to generate the hash since the probability of collision is extremely low. + if (sMessageDigest == null) { + sMessageDigest = MessageDigest.getInstance("SHA-256"); + } + final byte[] digest = sMessageDigest.digest(namespace.getBytes()); + // Convert to hexadecimal representation + StringBuilder hexBuilder = new StringBuilder(digest.length); + for (byte byteChar : digest) { + hexBuilder.append(String.format("%02X", byteChar)); + } + hash = hexBuilder.toString(); + } catch (Exception e) { + Slog.wtf(TAG, "Couldn't hash input", e); + } + if (hash == null) { + // If we get to this point, something went wrong with the MessageDigest above. + // Don't return the raw input value (which would defeat the purpose of hashing). + return "failed_namespace_hash"; + } + hash = hash.intern(); + synchronized (sNamespaceHashCache) { + if (sNamespaceHashCache.size() >= MAX_NAMESPACE_CACHE_SIZE) { + // Drop a random mapping instead of dropping at a predefined index to avoid + // potentially always dropping the same mapping. + sNamespaceHashCache.removeAt((new Random()).nextInt(MAX_NAMESPACE_CACHE_SIZE)); + } + sNamespaceHashCache.put(namespace, hash); + } + return hash; + } + public void enqueueWorkLocked(JobWorkItem work) { if (pendingWork == null) { pendingWork = new ArrayList<>(); @@ -1117,10 +1181,16 @@ public final class JobStatus { return true; } + @Nullable public String getNamespace() { return mNamespace; } + @Nullable + public String getNamespaceHash() { + return mNamespaceHash; + } + public String getSourceTag() { return sourceTag; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 9eb9d66cb71a..9bd29700a12d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3568,6 +3568,8 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000 field public CharSequence accessibilityTitle; + field public float preferredMaxDisplayRefreshRate; + field public float preferredMinDisplayRefreshRate; field public int privateFlags; } diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 12026aa3f72a..4cad58521c09 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -568,13 +568,13 @@ public abstract class Animator implements Cloneable { * repetition. lastPlayTime is similar and is used to calculate how many repeats have been * done between the two times. */ - void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {} + void animateValuesInRange(long currentPlayTime, long lastPlayTime) {} /** * Internal use only. This animates any animation that has ended since lastPlayTime. * If an animation hasn't been finished, no change will be made. */ - void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {} + void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {} /** * Internal use only. Adds all start times (after delay) to and end times to times. diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index f69bbfd3c53f..2198fcd70637 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -111,19 +111,20 @@ public class AnimatorInflater { float pathErrorScale) throws NotFoundException { final ConfigurationBoundResourceCache<Animator> animatorCache = resources .getAnimatorCache(); - Animator animator = animatorCache.getInstance(id, resources, theme); - if (animator != null) { + ConfigurationBoundResourceCache.Entry<Animator> animatorEntry = + animatorCache.getInstance(id, resources, theme); + if (animatorEntry.hasValue()) { if (DBG_ANIMATOR_INFLATER) { Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id)); } - return animator; + return animatorEntry.getValue(); } else if (DBG_ANIMATOR_INFLATER) { Log.d(TAG, "cache miss for animator " + resources.getResourceName(id)); } XmlResourceParser parser = null; try { parser = resources.getAnimation(id); - animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale); + Animator animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale); if (animator != null) { animator.appendChangingConfigurations(getChangingConfigs(resources, id)); final ConstantState<Animator> constantState = animator.createConstantState(); @@ -131,7 +132,7 @@ public class AnimatorInflater { if (DBG_ANIMATOR_INFLATER) { Log.d(TAG, "caching animator for res " + resources.getResourceName(id)); } - animatorCache.put(id, theme, constantState); + animatorCache.put(id, theme, constantState, animatorEntry.getGeneration()); // create a new animator so that cached version is never used by the user animator = constantState.newInstance(resources, theme); } @@ -160,20 +161,22 @@ public class AnimatorInflater { final ConfigurationBoundResourceCache<StateListAnimator> cache = resources .getStateListAnimatorCache(); final Theme theme = context.getTheme(); - StateListAnimator animator = cache.getInstance(id, resources, theme); - if (animator != null) { - return animator; + ConfigurationBoundResourceCache.Entry<StateListAnimator> animatorEntry = + cache.getInstance(id, resources, theme); + if (animatorEntry.hasValue()) { + return animatorEntry.getValue(); } XmlResourceParser parser = null; try { parser = resources.getAnimation(id); - animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser)); + StateListAnimator animator = + createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser)); if (animator != null) { animator.appendChangingConfigurations(getChangingConfigs(resources, id)); final ConstantState<StateListAnimator> constantState = animator .createConstantState(); if (constantState != null) { - cache.put(id, theme, constantState); + cache.put(id, theme, constantState, animatorEntry.getGeneration()); // return a clone so that the animator in constant state is never used. animator = constantState.newInstance(resources, theme); } diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 60659dc12342..70c3d7ae3f82 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -825,8 +825,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void animateBasedOnPlayTime( long currentPlayTime, long lastPlayTime, - boolean inReverse, - boolean notify + boolean inReverse ) { if (currentPlayTime < 0 || lastPlayTime < -1) { throw new UnsupportedOperationException("Error: Play time should never be negative."); @@ -857,8 +856,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim while (index < endIndex) { long playTime = startEndTimes[index]; if (lastPlayTime != playTime) { - animateSkipToEnds(playTime, lastPlayTime, notify); - animateValuesInRange(playTime, lastPlayTime, notify); + animateSkipToEnds(playTime, lastPlayTime); + animateValuesInRange(playTime, lastPlayTime); lastPlayTime = playTime; } index++; @@ -868,15 +867,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim index--; long playTime = startEndTimes[index]; if (lastPlayTime != playTime) { - animateSkipToEnds(playTime, lastPlayTime, notify); - animateValuesInRange(playTime, lastPlayTime, notify); + animateSkipToEnds(playTime, lastPlayTime); + animateValuesInRange(playTime, lastPlayTime); lastPlayTime = playTime; } } } if (currentPlayTime != lastPlayTime) { - animateSkipToEnds(currentPlayTime, lastPlayTime, notify); - animateValuesInRange(currentPlayTime, lastPlayTime, notify); + animateSkipToEnds(currentPlayTime, lastPlayTime); + animateValuesInRange(currentPlayTime, lastPlayTime); } } @@ -896,13 +895,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } @Override - void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) { + void animateSkipToEnds(long currentPlayTime, long lastPlayTime) { initAnimation(); if (lastPlayTime > currentPlayTime) { - if (notify) { - notifyStartListeners(true); - } + notifyStartListeners(true); for (int i = mEvents.size() - 1; i >= 0; i--) { AnimationEvent event = mEvents.get(i); Node node = event.mNode; @@ -916,31 +913,25 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (currentPlayTime <= start && start < lastPlayTime) { animator.animateSkipToEnds( 0, - lastPlayTime - node.mStartTime, - notify + lastPlayTime - node.mStartTime ); - if (notify) { - mPlayingSet.remove(node); - } + mPlayingSet.remove(node); } else if (start <= currentPlayTime && currentPlayTime <= end) { animator.animateSkipToEnds( currentPlayTime - node.mStartTime, - lastPlayTime - node.mStartTime, - notify + lastPlayTime - node.mStartTime ); - if (notify && !mPlayingSet.contains(node)) { + if (!mPlayingSet.contains(node)) { mPlayingSet.add(node); } } } } - if (currentPlayTime <= 0 && notify) { + if (currentPlayTime <= 0) { notifyEndListeners(true); } } else { - if (notify) { - notifyStartListeners(false); - } + notifyStartListeners(false); int eventsSize = mEvents.size(); for (int i = 0; i < eventsSize; i++) { AnimationEvent event = mEvents.get(i); @@ -955,45 +946,39 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (lastPlayTime < end && end <= currentPlayTime) { animator.animateSkipToEnds( end - node.mStartTime, - lastPlayTime - node.mStartTime, - notify + lastPlayTime - node.mStartTime ); - if (notify) { - mPlayingSet.remove(node); - } + mPlayingSet.remove(node); } else if (start <= currentPlayTime && currentPlayTime <= end) { animator.animateSkipToEnds( currentPlayTime - node.mStartTime, - lastPlayTime - node.mStartTime, - notify + lastPlayTime - node.mStartTime ); - if (notify && !mPlayingSet.contains(node)) { + if (!mPlayingSet.contains(node)) { mPlayingSet.add(node); } } } } - if (currentPlayTime >= getTotalDuration() && notify) { + if (currentPlayTime >= getTotalDuration()) { notifyEndListeners(false); } } } @Override - void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) { + void animateValuesInRange(long currentPlayTime, long lastPlayTime) { initAnimation(); - if (notify) { - if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) { - notifyStartListeners(false); - } else { - long duration = getTotalDuration(); - if (duration >= 0 - && (lastPlayTime > duration || (lastPlayTime == duration - && currentPlayTime < duration)) - ) { - notifyStartListeners(true); - } + if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) { + notifyStartListeners(false); + } else { + long duration = getTotalDuration(); + if (duration >= 0 + && (lastPlayTime > duration || (lastPlayTime == duration + && currentPlayTime < duration)) + ) { + notifyStartListeners(true); } } @@ -1014,8 +999,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim ) { animator.animateValuesInRange( currentPlayTime - node.mStartTime, - Math.max(-1, lastPlayTime - node.mStartTime), - notify + Math.max(-1, lastPlayTime - node.mStartTime) ); } } @@ -1111,7 +1095,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } mSeekState.setPlayTime(playTime, mReversing); - animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true); + animateBasedOnPlayTime(playTime, lastPlayTime, mReversing); } /** @@ -1144,16 +1128,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim private void initChildren() { if (!isInitialized()) { mChildrenInitialized = true; - - // We have to initialize all the start values so that they are based on the previous - // values. - long[] times = ensureChildStartAndEndTimes(); - - long previousTime = -1; - for (long time : times) { - animateBasedOnPlayTime(time, previousTime, false, false); - previousTime = time; - } + skipToEndValue(false); } } @@ -1489,6 +1464,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim anim.mPauseTime = -1; anim.mSeekState = new SeekState(); anim.mSelfPulse = true; + anim.mStartListenersCalled = false; anim.mPlayingSet = new ArrayList<Node>(); anim.mNodeMap = new ArrayMap<Animator, Node>(); anim.mNodes = new ArrayList<Node>(nodeCount); diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index ead238f75ba4..5de7f387b206 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -1417,21 +1417,19 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio * will be called. */ @Override - void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) { + void animateValuesInRange(long currentPlayTime, long lastPlayTime) { if (currentPlayTime < 0 || lastPlayTime < -1) { throw new UnsupportedOperationException("Error: Play time should never be negative."); } initAnimation(); long duration = getTotalDuration(); - if (notify) { - if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) { - notifyStartListeners(false); - } else if (lastPlayTime > duration - || (lastPlayTime == duration && currentPlayTime < duration) - ) { - notifyStartListeners(true); - } + if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) { + notifyStartListeners(false); + } else if (lastPlayTime > duration + || (lastPlayTime == duration && currentPlayTime < duration) + ) { + notifyStartListeners(true); } if (duration >= 0) { lastPlayTime = Math.min(duration, lastPlayTime); @@ -1448,7 +1446,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio iteration = Math.min(iteration, mRepeatCount); lastIteration = Math.min(lastIteration, mRepeatCount); - if (notify && iteration != lastIteration) { + if (iteration != lastIteration) { notifyListeners(AnimatorCaller.ON_REPEAT, false); } } @@ -1464,7 +1462,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio } @Override - void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) { + void animateSkipToEnds(long currentPlayTime, long lastPlayTime) { boolean inReverse = currentPlayTime < lastPlayTime; boolean doSkip; if (currentPlayTime <= 0 && lastPlayTime > 0) { @@ -1474,13 +1472,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration; } if (doSkip) { - if (notify) { - notifyStartListeners(inReverse); - } + notifyStartListeners(inReverse); skipToEndValue(inReverse); - if (notify) { - notifyEndListeners(inReverse); - } + notifyEndListeners(inReverse); } } diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 77b1954c0f1b..2669040403ea 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -184,7 +184,7 @@ public final class UserProperties implements Parcelable { * device policies from its parent profile. * *<p> All the user restrictions and device policies would be not propagated to the profile - * with this property value. The {(TODO:b/256978256) @link DevicePolicyEngine} + * with this property value. The {@link com.android.server.devicepolicy.DevicePolicyEngine} * uses this property to determine and propagate only select ones to the given profile. * * @hide diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java index 5e10a5768358..4da3c18883bd 100644 --- a/core/java/android/content/res/ConfigurationBoundResourceCache.java +++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java @@ -37,16 +37,16 @@ public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<Cons * @param key a key that uniquely identifies the drawable resource * @param resources a Resources object from which to create new instances. * @param theme the theme where the resource will be used - * @return a new instance of the resource, or {@code null} if not in + * @return an Entry wrapping a new instance of the resource, or {@code null} if not in * the cache */ - public T getInstance(long key, Resources resources, Resources.Theme theme) { - final ConstantState<T> entry = get(key, theme); - if (entry != null) { - return entry.newInstance(resources, theme); + public Entry<T> getInstance(long key, Resources resources, Resources.Theme theme) { + final Entry<ConstantState<T>> e = get(key, theme); + if (e.hasValue()) { + return new Entry<>(e.getValue().newInstance(resources, theme), e.getGeneration()); } - return null; + return new Entry<>(null, e.getGeneration()); } @Override diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java index d0ebe3304065..b51d14a3c472 100644 --- a/core/java/android/content/res/DrawableCache.java +++ b/core/java/android/content/res/DrawableCache.java @@ -40,14 +40,32 @@ class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Drawable getInstance(long key, Resources resources, Resources.Theme theme) { - final Drawable.ConstantState entry = get(key, theme); - if (entry != null) { - return entry.newDrawable(resources, theme); + final Entry<Drawable.ConstantState> entry = get(key, theme); + if (entry.getValue() != null) { + return entry.getValue().newDrawable(resources, theme); } return null; } + /** + * If the resource is cached, creates and returns a new instance of it. + * + * @param key a key that uniquely identifies the drawable resource + * @param resources a Resources object from which to create new instances. + * @param theme the theme where the resource will be used + * @return an Entry wrapping a a new instance of the resource, or {@code null} if not in + * the cache + */ + public Entry<Drawable> getDrawable(long key, Resources resources, Resources.Theme theme) { + final Entry<Drawable.ConstantState> e = get(key, theme); + if (e.hasValue()) { + return new Entry<>(e.getValue().newDrawable(resources, theme), e.getGeneration()); + } + + return new Entry<>(null, e.getGeneration()); + } + @Override public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) { return Configuration.needNewResources(configChanges, entry.getChangingConfigurations()); diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 3a2863e5636d..25154d5c1623 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -650,15 +650,21 @@ public class ResourcesImpl { key = (((long) value.assetCookie) << 32) | value.data; } + int cacheGeneration; // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. Skip the cache if // we're currently preloading or we're not using the cache. if (!mPreloading && useCache) { - final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme); - if (cachedDrawable != null) { - cachedDrawable.setChangingConfigurations(value.changingConfigurations); - return cachedDrawable; + final ThemedResourceCache.Entry<Drawable> cachedDrawable = + caches.getDrawable(key, wrapper, theme); + if (cachedDrawable.hasValue()) { + cachedDrawable.getValue().setChangingConfigurations( + value.changingConfigurations); + return cachedDrawable.getValue(); } + cacheGeneration = cachedDrawable.getGeneration(); + } else { + cacheGeneration = ThemedResourceCache.UNDEFINED_GENERATION; } // Next, check preloaded drawables. Preloaded drawables may contain @@ -702,7 +708,8 @@ public class ResourcesImpl { if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); if (useCache) { - cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); + cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr, + cacheGeneration); if (needsNewDrawableAfterCache) { Drawable.ConstantState state = dr.getConstantState(); if (state != null) { @@ -733,7 +740,7 @@ public class ResourcesImpl { } private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, - Resources.Theme theme, boolean usesTheme, long key, Drawable dr) { + Resources.Theme theme, boolean usesTheme, long key, Drawable dr, int cacheGeneration) { final Drawable.ConstantState cs = dr.getConstantState(); if (cs == null) { return; @@ -761,7 +768,7 @@ public class ResourcesImpl { } } else { synchronized (mAccessLock) { - caches.put(key, theme, cs, usesTheme); + caches.put(key, theme, cs, cacheGeneration, usesTheme); } } } @@ -1002,14 +1009,16 @@ public class ResourcesImpl { TypedValue value, int id) { final long key = (((long) value.assetCookie) << 32) | value.data; final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache; - ComplexColor complexColor = cache.getInstance(key, wrapper, theme); - if (complexColor != null) { - return complexColor; + ThemedResourceCache.Entry<ComplexColor> complexColorEntry = + cache.getInstance(key, wrapper, theme); + if (complexColorEntry.hasValue()) { + return complexColorEntry.getValue(); } final android.content.res.ConstantState<ComplexColor> factory = sPreloadedComplexColors.get(key); + ComplexColor complexColor = null; if (factory != null) { complexColor = factory.newInstance(wrapper, theme); } @@ -1026,7 +1035,8 @@ public class ResourcesImpl { sPreloadedComplexColors.put(key, complexColor.getConstantState()); } } else { - cache.put(key, theme, complexColor.getConstantState()); + cache.put(key, theme, complexColor.getConstantState(), + complexColorEntry.getGeneration()); } } return complexColor; diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java index 3270944ce7e3..e7fd2755a941 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -33,11 +33,37 @@ import java.lang.ref.WeakReference; * @param <T> type of data to cache */ abstract class ThemedResourceCache<T> { + public static final int UNDEFINED_GENERATION = -1; @UnsupportedAppUsage private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries; private LongSparseArray<WeakReference<T>> mUnthemedEntries; private LongSparseArray<WeakReference<T>> mNullThemedEntries; + private int mGeneration; + + public static class Entry<S> { + private S mValue; + private int mGeneration; + + + public S getValue() { + return mValue; + } + + public boolean hasValue() { + return mValue != null; + } + + public int getGeneration() { + return mGeneration; + } + + Entry(S value, int generation) { + this.mValue = value; + this.mGeneration = generation; + } + } + /** * Adds a new theme-dependent entry to the cache. * @@ -45,9 +71,10 @@ abstract class ThemedResourceCache<T> { * @param theme the theme against which this entry was inflated, or * {@code null} if the entry has no theme applied * @param entry the entry to cache + * @param generation The generation of the cache to compare against before storing */ - public void put(long key, @Nullable Theme theme, @NonNull T entry) { - put(key, theme, entry, true); + public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation) { + put(key, theme, entry, generation, true); } /** @@ -57,10 +84,12 @@ abstract class ThemedResourceCache<T> { * @param theme the theme against which this entry was inflated, or * {@code null} if the entry has no theme applied * @param entry the entry to cache + * @param generation The generation of the cache to compare against before storing * @param usesTheme {@code true} if the entry is affected theme changes, * {@code false} otherwise */ - public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) { + public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation, + boolean usesTheme) { if (entry == null) { return; } @@ -72,7 +101,8 @@ abstract class ThemedResourceCache<T> { } else { entries = getThemedLocked(theme, true); } - if (entries != null) { + if (entries != null + && ((generation == mGeneration) || (generation == UNDEFINED_GENERATION))) { entries.put(key, new WeakReference<>(entry)); } } @@ -86,7 +116,7 @@ abstract class ThemedResourceCache<T> { * @return a cached entry, or {@code null} if not in the cache */ @Nullable - public T get(long key, @Nullable Theme theme) { + public Entry get(long key, @Nullable Theme theme) { // The themed (includes null-themed) and unthemed caches are mutually // exclusive, so we'll give priority to whichever one we think we'll // hit first. Since most of the framework drawables are themed, that's @@ -96,7 +126,7 @@ abstract class ThemedResourceCache<T> { if (themedEntries != null) { final WeakReference<T> themedEntry = themedEntries.get(key); if (themedEntry != null) { - return themedEntry.get(); + return new Entry(themedEntry.get(), mGeneration); } } @@ -104,12 +134,12 @@ abstract class ThemedResourceCache<T> { if (unthemedEntries != null) { final WeakReference<T> unthemedEntry = unthemedEntries.get(key); if (unthemedEntry != null) { - return unthemedEntry.get(); + return new Entry(unthemedEntry.get(), mGeneration); } } } - return null; + return new Entry(null, mGeneration); } /** @@ -121,6 +151,7 @@ abstract class ThemedResourceCache<T> { @UnsupportedAppUsage public void onConfigurationChange(@Config int configChanges) { prune(configChanges); + mGeneration++; } /** diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7d68b44581de..84f1880213b8 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -736,7 +736,7 @@ public class UserManager { * The default value is <code>false</code>. * * <p>Holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS} + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS} * can set this restriction using the DevicePolicyManager APIs mentioned below. * * <p>Key for user restrictions. @@ -941,7 +941,7 @@ public class UserManager { * this restriction will be set as a base restriction which cannot be removed by any admin. * * <p>Holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS} + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS} * can set this restriction using the DevicePolicyManager APIs mentioned below. * * <p>Key for user restrictions. @@ -1451,7 +1451,7 @@ public class UserManager { * affected. The default value is <code>false</code>. * * <p>Holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS} + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS} * can set this restriction using the DevicePolicyManager APIs mentioned below. * * <p>Key for user restrictions. @@ -1597,7 +1597,7 @@ public class UserManager { * set. * * <p>Holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS} + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS} * can set this restriction using the DevicePolicyManager APIs mentioned below. * * <p>The default value is <code>false</code>. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4cbb0409dafc..1bbb7b4bd671 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2426,7 +2426,7 @@ public final class ViewRootImpl implements ViewParent, * * @hide */ - void notifyRendererOfExpensiveFrame() { + public void notifyRendererOfExpensiveFrame() { if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.notifyExpensiveFrame(); } @@ -7017,6 +7017,10 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; boolean handled = mHandwritingInitiator.onTouchEvent(event); + if (handled) { + // If handwriting is started, toolkit doesn't receive ACTION_UP. + mLastClickToolType = event.getToolType(event.getActionIndex()); + } mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e40f8e78dafc..6714c393d64b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3763,6 +3763,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @TestApi public float preferredMinDisplayRefreshRate; /** @@ -3771,6 +3772,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @TestApi public float preferredMaxDisplayRefreshRate; /** Indicates whether this window wants the HDR conversion is disabled. */ diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 5ad74c8803f4..461b78ce4fc2 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -283,6 +283,9 @@ public class AutofillFeatureFlags { private static final String DEFAULT_AFAA_NON_AUTOFILLABLE_IME_ACTIONS = "3,4"; private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES = true; private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_MULTILINE_FILTER = true; + private static final boolean + DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true; + // END AUTOFILL FOR ALL APPS DEFAULTS private AutofillFeatureFlags() {}; @@ -414,7 +417,8 @@ public class AutofillFeatureFlags { public static boolean shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue() { return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_AUTOFILL, - DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE, false); + DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE, + DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE); } /** diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java index a7538701807a..0525443ecf82 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import android.util.Property; import android.view.View; import androidx.test.annotation.UiThreadTest; @@ -576,6 +577,42 @@ public class AnimatorSetActivityTest { }); } + @Test + public void testInitializeWithoutReadingValues() throws Throwable { + // Some consumers crash while reading values before the animator starts + Property<int[], Integer> property = new Property<>(Integer.class, "firstValue") { + @Override + public Integer get(int[] target) { + throw new IllegalStateException("Shouldn't be called"); + } + + @Override + public void set(int[] target, Integer value) { + target[0] = value; + } + }; + + int[] target1 = new int[1]; + int[] target2 = new int[1]; + int[] target3 = new int[1]; + ObjectAnimator animator1 = ObjectAnimator.ofInt(target1, property, 0, 100); + ObjectAnimator animator2 = ObjectAnimator.ofInt(target2, property, 0, 100); + ObjectAnimator animator3 = ObjectAnimator.ofInt(target3, property, 0, 100); + AnimatorSet set = new AnimatorSet(); + set.playSequentially(animator1, animator2, animator3); + + mActivityRule.runOnUiThread(() -> { + set.setCurrentPlayTime(900); + assertEquals(100, target1[0]); + assertEquals(100, target2[0]); + assertEquals(100, target3[0]); + set.setCurrentPlayTime(0); + assertEquals(0, target1[0]); + assertEquals(0, target2[0]); + assertEquals(0, target3[0]); + }); + } + /** * Check that the animator list contains exactly the given animators and nothing else. */ diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java index 4f8b85554f5c..b5f18c26dc97 100644 --- a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java +++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java @@ -45,36 +45,40 @@ public class ConfigurationBoundResourceCacheTest @SmallTest public void testGetEmpty() { final Resources res = getActivity().getResources(); - assertNull(mCache.getInstance(-1, res, null)); + assertNull(mCache.getInstance(-1, res, null).getValue()); } @SmallTest public void testSetGet() { - mCache.put(1, null, new DummyFloatConstantState(5f)); + mCache.put(1, null, new DummyFloatConstantState(5f), + ThemedResourceCache.UNDEFINED_GENERATION); final Resources res = getActivity().getResources(); - assertEquals(5f, mCache.getInstance(1, res, null)); - assertNotSame(5f, mCache.getInstance(1, res, null)); - assertEquals(null, mCache.getInstance(1, res, getActivity().getTheme())); + assertEquals(5f, mCache.getInstance(1, res, null).getValue()); + assertNotSame(5f, mCache.getInstance(1, res, null).getValue()); + assertEquals(false, mCache.getInstance(1, res, getActivity().getTheme()).hasValue()); } @SmallTest public void testSetGetThemed() { - mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); + mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f), + ThemedResourceCache.UNDEFINED_GENERATION); final Resources res = getActivity().getResources(); - assertEquals(null, mCache.getInstance(1, res, null)); - assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme())); - assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme())); + assertEquals(false, mCache.getInstance(1, res, null).hasValue()); + assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue()); + assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue()); } @SmallTest public void testMultiThreadPutGet() { - mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); - mCache.put(1, null, new DummyFloatConstantState(10f)); + mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f), + ThemedResourceCache.UNDEFINED_GENERATION); + mCache.put(1, null, new DummyFloatConstantState(10f), + ThemedResourceCache.UNDEFINED_GENERATION); final Resources res = getActivity().getResources(); - assertEquals(10f, mCache.getInstance(1, res, null)); - assertNotSame(10f, mCache.getInstance(1, res, null)); - assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme())); - assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme())); + assertEquals(10f, mCache.getInstance(1, res, null).getValue()); + assertNotSame(10f, mCache.getInstance(1, res, null).getValue()); + assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue()); + assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue()); } @SmallTest @@ -86,16 +90,17 @@ public class ConfigurationBoundResourceCacheTest res.getValue(R.dimen.resource_cache_test_generic, staticValue, true); float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics()); mCache.put(key, getActivity().getTheme(), - new DummyFloatConstantState(staticDim, staticValue.changingConfigurations)); + new DummyFloatConstantState(staticDim, staticValue.changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); final Configuration cfg = res.getConfiguration(); Configuration newCnf = new Configuration(cfg); newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; int changes = calcConfigChanges(res, newCnf); - assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme())); + assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue()); mCache.onConfigurationChange(changes); - assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme())); + assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue()); } @SmallTest @@ -108,7 +113,8 @@ public class ConfigurationBoundResourceCacheTest float changingDim = TypedValue.complexToDimension(changingValue.data, res.getDisplayMetrics()); mCache.put(key, getActivity().getTheme(), - new DummyFloatConstantState(changingDim, changingValue.changingConfigurations)); + new DummyFloatConstantState(changingDim, changingValue.changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); final Configuration cfg = res.getConfiguration(); Configuration newCnf = new Configuration(cfg); @@ -116,9 +122,10 @@ public class ConfigurationBoundResourceCacheTest Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; int changes = calcConfigChanges(res, newCnf); - assertEquals(changingDim, mCache.getInstance(key, res, getActivity().getTheme())); + assertEquals(changingDim, + mCache.getInstance(key, res, getActivity().getTheme()).getValue()); mCache.onConfigurationChange(changes); - assertNull(mCache.get(key, getActivity().getTheme())); + assertNull(mCache.get(key, getActivity().getTheme()).getValue()); } @SmallTest @@ -133,9 +140,11 @@ public class ConfigurationBoundResourceCacheTest float changingDim = TypedValue.complexToDimension(changingValue.data, res.getDisplayMetrics()); mCache.put(R.dimen.resource_cache_test_generic, getActivity().getTheme(), - new DummyFloatConstantState(staticDim, staticValue.changingConfigurations)); + new DummyFloatConstantState(staticDim, staticValue.changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); mCache.put(R.dimen.resource_cache_test_orientation_dependent, getActivity().getTheme(), - new DummyFloatConstantState(changingDim, changingValue.changingConfigurations)); + new DummyFloatConstantState(changingDim, changingValue.changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); final Configuration cfg = res.getConfiguration(); Configuration newCnf = new Configuration(cfg); newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? @@ -143,15 +152,15 @@ public class ConfigurationBoundResourceCacheTest : Configuration.ORIENTATION_LANDSCAPE; int changes = calcConfigChanges(res, newCnf); assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res, - getActivity().getTheme())); + getActivity().getTheme()).getValue()); assertEquals(changingDim, mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res, - getActivity().getTheme())); + getActivity().getTheme()).getValue()); mCache.onConfigurationChange(changes); assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res, - getActivity().getTheme())); + getActivity().getTheme()).getValue()); assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res, - getActivity().getTheme())); + getActivity().getTheme()).getValue()); } @SmallTest @@ -173,10 +182,12 @@ public class ConfigurationBoundResourceCacheTest res.getDisplayMetrics()); final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; mCache.put(R.dimen.resource_cache_test_generic, theme, - new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations)); + new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); mCache.put(R.dimen.resource_cache_test_orientation_dependent, theme, new DummyFloatConstantState(changingDim, - changingValues[i].changingConfigurations)); + changingValues[i].changingConfigurations), + ThemedResourceCache.UNDEFINED_GENERATION); } final Configuration cfg = res.getConfiguration(); Configuration newCnf = new Configuration(cfg); @@ -187,18 +198,18 @@ public class ConfigurationBoundResourceCacheTest for (int i = 0; i < 2; i++) { final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; assertEquals(staticDim, - mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme)); + mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue()); assertEquals(changingDim, mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res, - theme)); + theme).getValue()); } mCache.onConfigurationChange(changes); for (int i = 0; i < 2; i++) { final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; assertEquals(staticDim, - mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme)); + mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue()); assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res, - theme)); + theme).getValue()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 281cae5e4ffa..abe2db094a5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -70,6 +70,7 @@ public class PipResizeGestureHandler { private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final PipMotionHelper mMotionHelper; private final PipBoundsState mPipBoundsState; + private final PipTouchState mPipTouchState; private final PipTaskOrganizer mPipTaskOrganizer; private final PhonePipMenuController mPhonePipMenuController; private final PipDismissTargetHandler mPipDismissTargetHandler; @@ -104,7 +105,6 @@ public class PipResizeGestureHandler { private boolean mAllowGesture; private boolean mIsAttached; private boolean mIsEnabled; - private boolean mEnableTouch; private boolean mEnablePinchResize; private boolean mEnableDragCornerResize; private boolean mIsSysUiStateValid; @@ -122,7 +122,8 @@ public class PipResizeGestureHandler { public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper motionHelper, - PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler, + PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer, + PipDismissTargetHandler pipDismissTargetHandler, Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) { @@ -132,6 +133,7 @@ public class PipResizeGestureHandler { mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mMotionHelper = motionHelper; + mPipTouchState = pipTouchState; mPipTaskOrganizer = pipTaskOrganizer; mPipDismissTargetHandler = pipDismissTargetHandler; mMovementBoundsSupplier = movementBoundsSupplier; @@ -139,7 +141,6 @@ public class PipResizeGestureHandler { mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); - mEnableTouch = true; mUpdateResizeBoundsCallback = (rect) -> { mUserResizeBounds.set(rect); @@ -250,8 +251,8 @@ public class PipResizeGestureHandler { return; } - if (!mEnableTouch) { - // No need to handle anything if touches are not enabled for resizing. + if (!mPipTouchState.getAllowInputEvents()) { + // No need to handle anything if touches are not enabled return; } @@ -588,13 +589,13 @@ public class PipResizeGestureHandler { mLastResizeBounds, movementBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); - // disable the resizing until the final bounds are updated - mEnableTouch = false; + // disable any touch events beyond resizing too + mPipTouchState.setAllowInputEvents(false); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { - // reset the pinch resizing to its default state - mEnableTouch = true; + // enable touch events + mPipTouchState.setAllowInputEvents(true); }); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 466da0e85358..2c4f76b1f34b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -199,11 +199,6 @@ public class PipTouchHandler { mMotionHelper = pipMotionHelper; mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, mMotionHelper, mainExecutor); - mPipResizeGestureHandler = - new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, - mMotionHelper, pipTaskOrganizer, mPipDismissTargetHandler, - this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger, - menuController, mainExecutor); mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> { if (mPipBoundsState.isStashed()) { @@ -220,6 +215,11 @@ public class PipTouchHandler { }, menuController::hideMenu, mainExecutor); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, + mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler, + this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger, + menuController, mainExecutor); mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), this::onAccessibilityShowMenu, this::updateMovementBounds, @@ -556,6 +556,11 @@ public class PipTouchHandler { return true; } + // do not process input event if not allowed + if (!mTouchState.getAllowInputEvents()) { + return true; + } + MotionEvent ev = (MotionEvent) inputEvent; if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) { // Initialize the touch state for the gesture, but immediately reset to invalidate the diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java index d7d69f27f9f8..7f62c629c7f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java @@ -37,7 +37,7 @@ public class PipTouchState { private static final boolean DEBUG = false; @VisibleForTesting - public static final long DOUBLE_TAP_TIMEOUT = 200; + public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); static final long HOVER_EXIT_TIMEOUT = 50; private final ShellExecutor mMainExecutor; @@ -55,6 +55,9 @@ public class PipTouchState { private final PointF mLastDelta = new PointF(); private final PointF mVelocity = new PointF(); private boolean mAllowTouches = true; + + // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing + private boolean mAllowInputEvents = true; private boolean mIsUserInteracting = false; // Set to true only if the multiple taps occur within the double tap timeout private boolean mIsDoubleTap = false; @@ -77,6 +80,20 @@ public class PipTouchState { } /** + * @return true if input processing is enabled for PiP in general. + */ + public boolean getAllowInputEvents() { + return mAllowInputEvents; + } + + /** + * @param allowInputEvents true to enable input processing for PiP in general. + */ + public void setAllowInputEvents(boolean allowInputEvents) { + mAllowInputEvents = allowInputEvents; + } + + /** * Resets this state. */ public void reset() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 366f4f143374..ff21c9e80ef2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -2407,7 +2406,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { mSplitLayout.update(startTransaction); - record.mContainDisplayChange = true; } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -2439,8 +2437,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final StageTaskListener stage = getStageOfTask(taskInfo); if (stage == null) { if (change.getParent() == null && !isClosingType(change.getMode()) - && taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - record.mContainShowPipChange = true; + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + record.mContainShowFullscreenChange = true; } continue; } @@ -2459,15 +2457,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage(); - if (!record.mContainDisplayChange && record.mContainShowPipChange) { - // This occurred when split enter pip by open another fullscreen app so let - // pip tranistion handler to handle this but also start our dismiss transition. - // TODO(b/282894249): Should improve this case animation on pip. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); - mSplitTransitions.startDismissTransition(wct, this, STAGE_TYPE_UNDEFINED, - EXIT_REASON_CHILD_TASK_ENTER_PIP); - } else if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0 + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0 || dismissStages.size() == 1) { // If the size of dismissStages == 1, one of the task is closed without prepare // pending transition, which could happen if all activities were finished after @@ -2480,9 +2470,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = (dismissStages.size() == 1 && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN) || mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; - prepareExitSplitScreen(dismissTop, wct); + // If there is a fullscreen opening change, we should not bring stage to top. + prepareExitSplitScreen(record.mContainShowFullscreenChange + ? STAGE_TYPE_UNDEFINED : dismissTop, wct); mSplitTransitions.startDismissTransition(wct, this, dismissTop, - EXIT_REASON_UNKNOWN); + EXIT_REASON_APP_FINISHED); // This can happen in some pathological cases. For example: // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time @@ -2506,8 +2498,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } static class StageChangeRecord { - boolean mContainDisplayChange = false; - boolean mContainShowPipChange = false; + boolean mContainShowFullscreenChange = false; static class StageChange { final StageTaskListener mStageTaskListener; final IntArray mAddedTaskId = new IntArray(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index bb0eba6a0fc7..c504f57216f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -173,6 +173,17 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene mTransition = null; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull TransitionFinishCallback finishCallback) { + if (info.getType() == TRANSIT_CHANGE) { + // Apply changes happening during the unfold animation immediately + t.apply(); + finishCallback.onTransitionFinished(null, null); + } + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index a4ac261d1946..ef7bedf49a92 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder @@ -55,20 +54,24 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) { - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - pipApp.launchViaIntent(wmHelper) - pipApp.enableAutoEnterForPipActivity() - } - teardown { - // close gracefully so that onActivityUnpinned() can be called before force exit - pipApp.closePipWindow(wmHelper) - pipApp.exit(wmHelper) - } - transitions { tapl.goHome() } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { tapl.goHome() } + } + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + pipApp.launchViaIntent(wmHelper) + pipApp.enableAutoEnterForPipActivity() } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + // close gracefully so that onActivityUnpinned() can be called before force exit + pipApp.closePipWindow(wmHelper) + pipApp.exit(wmHelper) + } + } @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index 98fc91b334cf..afcc1729ed16 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -54,40 +54,38 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - val pipRegion = wmHelper.getWindowRegion(pipApp).bounds - val pipCenterX = pipRegion.centerX() - val pipCenterY = pipRegion.centerY() - val displayCenterX = device.displayWidth / 2 - val barComponent = - if (flicker.scenario.isTablet) { - ComponentNameMatcher.TASK_BAR - } else { - ComponentNameMatcher.NAV_BAR - } - val barLayerHeight = - wmHelper.currentState.layerState - .getLayerWithBuffer(barComponent) - ?.visibleRegion - ?.height - ?: error("Couldn't find Nav or Task bar layer") - // The dismiss button doesn't appear at the complete bottom of the screen, - // it appears above the hot seat but `hotseatBarSize` is not available outside - // the platform - val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight - device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50) - // Wait until the other app is no longer visible - wmHelper - .StateSyncBuilder() - .withPipGone() - .withWindowSurfaceDisappeared(pipApp) - .withAppTransitionIdle() - .waitForAndVerify() - } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { + val pipRegion = wmHelper.getWindowRegion(pipApp).bounds + val pipCenterX = pipRegion.centerX() + val pipCenterY = pipRegion.centerY() + val displayCenterX = device.displayWidth / 2 + val barComponent = + if (flicker.scenario.isTablet) { + ComponentNameMatcher.TASK_BAR + } else { + ComponentNameMatcher.NAV_BAR + } + val barLayerHeight = + wmHelper.currentState.layerState + .getLayerWithBuffer(barComponent) + ?.visibleRegion + ?.height + ?: error("Couldn't find Nav or Task bar layer") + // The dismiss button doesn't appear at the complete bottom of the screen, + // it appears above the hot seat but `hotseatBarSize` is not available outside + // the platform + val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight + device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50) + // Wait until the other app is no longer visible + wmHelper + .StateSyncBuilder() + .withPipGone() + .withWindowSurfaceDisappeared(pipApp) + .withAppTransitionIdle() + .waitForAndVerify() } + } /** Checks that the focus doesn't change between windows during the transition */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt index 2417c45bf9a0..e52b71e602f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt @@ -28,11 +28,10 @@ import org.junit.runners.Parameterized /** Base class for exiting pip (closing pip window) without returning to the app */ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) { - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - setup { this.setRotation(flicker.scenario.startRotation) } - teardown { this.setRotation(Rotation.ROTATION_0) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { this.setRotation(flicker.scenario.startRotation) } + teardown { this.setRotation(Rotation.ROTATION_0) } + } /** * Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index d16583271e8c..86fe583c94e6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -54,12 +54,9 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { pipApp.closePipWindow(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.closePipWindow(wmHelper) } + } /** * Checks that the focus changes between the pip menu window and the launcher when clicking the diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index f52e877ec2b1..01d67cc35a14 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -45,20 +45,24 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) { - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - pipApp.launchViaIntent(wmHelper) - pipApp.enableEnterPipOnUserLeaveHint() - } - teardown { - // close gracefully so that onActivityUnpinned() can be called before force exit - pipApp.closePipWindow(wmHelper) - pipApp.exit(wmHelper) - } - transitions { tapl.goHome() } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { tapl.goHome() } + } + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + pipApp.launchViaIntent(wmHelper) + pipApp.enableEnterPipOnUserLeaveHint() + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + // close gracefully so that onActivityUnpinned() can be called before force exit + pipApp.closePipWindow(wmHelper) + pipApp.exit(wmHelper) } + } @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 4b4613704a16..5480144ba1ce 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -73,39 +73,39 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - // Launch a portrait only app on the fullscreen stack - testApp.launchViaIntent( + override val thisTransition: FlickerBuilder.() -> Unit = { + teardown { + testApp.exit(wmHelper) + } + transitions { + // Enter PiP, and assert that the PiP is within bounds now that the device is back + // in portrait + broadcastActionTrigger.doAction(ACTION_ENTER_PIP) + // during rotation the status bar becomes invisible and reappears at the end + wmHelper + .StateSyncBuilder() + .withPipShown() + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + } + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + // Launch a portrait only app on the fullscreen stack + testApp.launchViaIntent( wmHelper, stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) - ) - // Launch the PiP activity fixed as landscape - pipApp.launchViaIntent( + ) + // Launch the PiP activity fixed as landscape, but don't enter PiP + pipApp.launchViaIntent( wmHelper, stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) - ) - } - teardown { - pipApp.exit(wmHelper) - testApp.exit(wmHelper) - } - transitions { - // Enter PiP, and assert that the PiP is within bounds now that the device is back - // in portrait - broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - // during rotation the status bar becomes invisible and reappears at the end - wmHelper - .StateSyncBuilder() - .withPipShown() - .withNavOrTaskBarVisible() - .withStatusBarVisible() - .waitForAndVerify() - } + mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + ) } + } /** * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt index bfd57786e615..95121def07bc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt @@ -26,12 +26,11 @@ import org.junit.Test import org.junit.runners.Parameterized abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) { - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { pipApp.launchViaIntent(wmHelper) } - teardown { pipApp.exit(wmHelper) } + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + pipApp.launchViaIntent(wmHelper) } + } /** Checks [pipApp] window remains visible throughout the animation */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index f1925d8c9d85..95725b64a48a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -51,11 +51,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) { - - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { pipApp.clickEnterPipButton(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.clickEnterPipButton(wmHelper) } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 3e0e37dfc997..0b3d16a8087d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -53,19 +53,16 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { - - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - setup { - // launch an app behind the pip one - testApp.launchViaIntent(wmHelper) - } - transitions { - // This will bring PipApp to fullscreen - pipApp.expandPipWindowToApp(wmHelper) - // Wait until the other app is no longer visible - wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() - } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) + } + transitions { + // This will bring PipApp to fullscreen + pipApp.expandPipWindowToApp(wmHelper) + // Wait until the other app is no longer visible + wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index 603f99541a12..bb2d40becdc9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -52,19 +52,16 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { - - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - setup { - // launch an app behind the pip one - testApp.launchViaIntent(wmHelper) - } - transitions { - // This will bring PipApp to fullscreen - pipApp.exitPipToFullScreenViaIntent(wmHelper) - // Wait until the other app is no longer visible - wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() - } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) + } + transitions { + // This will bring PipApp to fullscreen + pipApp.exitPipToFullScreenViaIntent(wmHelper) + // Wait until the other app is no longer visible + wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify() } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index bbb1c6c2ac63..fd16b6ea6ada 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -56,8 +56,9 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) { - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.doubleClickPipWindow(wmHelper) } + } /** * Checks that the pip app window remains inside the display bounds throughout the whole diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt index d860e00fbfff..253aa4cae5c7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt @@ -35,8 +35,9 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) { - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } + } /** Checks that the visible region area of [pipApp] always increases during the animation. */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index d8d57d219933..094060f86691 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -56,15 +56,10 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) { - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - teardown { - tapl.pressHome() - testApp.exit(wmHelper) - } - transitions { testApp.launchViaIntent(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + teardown { testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } + } /** Checks that the visible region of [pipApp] window always moves down during the animation. */ @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index 6b061bbb1565..ff51c27bf116 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -41,23 +41,21 @@ import org.junit.runners.Parameterized open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - setup { - imeApp.launchViaIntent(wmHelper) - setRotation(flicker.scenario.startRotation) - } - teardown { imeApp.exit(wmHelper) } - transitions { - // open the soft keyboard - imeApp.openIME(wmHelper) - createTag(TAG_IME_VISIBLE) + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + imeApp.launchViaIntent(wmHelper) + setRotation(flicker.scenario.startRotation) + } + teardown { imeApp.exit(wmHelper) } + transitions { + // open the soft keyboard + imeApp.openIME(wmHelper) + createTag(TAG_IME_VISIBLE) - // then close it again - imeApp.closeIME(wmHelper) - } + // then close it again + imeApp.closeIME(wmHelper) } + } /** Ensure the pip window remains visible throughout any keyboard interactions */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index ae3f87967658..27b061b67a85 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -57,14 +57,12 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) { - /** Defines the transition used to run the test */ - override val transition: FlickerBuilder.() -> Unit - get() = - buildTransition() { - setup { testApp.launchViaIntent(wmHelper) } - transitions { tapl.pressHome() } - teardown { testApp.exit(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = + { + setup { testApp.launchViaIntent(wmHelper) } + transitions { tapl.pressHome() } + teardown { testApp.exit(wmHelper) } + } /** Checks that the visible region of [pipApp] window always moves up during the animation. */ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 4e2a4e700698..9f81ba8eee87 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -22,7 +22,6 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import com.android.server.wm.flicker.testapp.ActivityOptions import org.junit.FixMethodOrder import org.junit.Test @@ -37,28 +36,31 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { private var isDraggedLeft: Boolean = true - override val transition: FlickerBuilder.() -> Unit - get() = { - val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") - setup { - tapl.setEnableRotation(true) - // Launch the PIP activity and wait for it to enter PiP mode - RemoveAllTasksButHomeRule.removeAllTasksButHome() - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) } + } - // determine the direction of dragging to test for - isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper) - } - teardown { - // release the primary pointer after dragging without release - pipApp.releasePipAfterDragging() + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") + setup { + tapl.setEnableRotation(true) + pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - pipApp.exit(wmHelper) - tapl.setEnableRotation(false) - } - transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) } + // determine the direction of dragging to test for + isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper) + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + // release the primary pointer after dragging without release + pipApp.releasePipAfterDragging() + + pipApp.exit(wmHelper) + tapl.setEnableRotation(false) } + } @Postsubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 8eb41b4ac694..60bf5ffdc7af 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -37,8 +37,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @FlakyTest(bugId = 270677470) class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } + } /** Checks that the visible region area of [pipApp] always decreases during the animation. */ @Postsubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index eb1245b9ab86..17a178f78de3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -56,27 +56,37 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { } } - /** - * Gets a configuration that handles basic setup and teardown of pip tests and that launches the - * Pip app for test - * - * @param stringExtras Arguments to pass to the PIP launch intent - * @param extraSpec Additional segment of flicker specification - */ - @JvmOverloads - protected open fun buildTransition( - stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"), - extraSpec: FlickerBuilder.() -> Unit = {} - ): FlickerBuilder.() -> Unit { - return { - setup { - setRotation(Rotation.ROTATION_0) - removeAllTasksButHome() - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - } - teardown { pipApp.exit(wmHelper) } + /** Defines the transition used to run the test */ + protected open val thisTransition: FlickerBuilder.() -> Unit = {} - extraSpec(this) + override val transition: FlickerBuilder.() -> Unit + get() = { + defaultSetup(this) + defaultEnterPip(this) + thisTransition(this) + defaultTeardown(this) + } + + /** Defines the default setup steps required by the test */ + protected open val defaultSetup: FlickerBuilder.() -> Unit = { + setup { + setRotation(Rotation.ROTATION_0) + removeAllTasksButHome() + } + } + + /** Defines the default method of entering PiP */ + protected open val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + pipApp.launchViaIntentAndWaitForPip(wmHelper, + stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")) + } + } + + /** Defines the default teardown required to clean up after the test */ + protected open val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + pipApp.exit(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt index 3850c1f6c89a..c618e5a24fdf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt @@ -50,41 +50,41 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - // Launch the PiP activity fixed as landscape. - pipApp.launchViaIntent( + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { + // Launch the activity back into fullscreen and ensure that it is now in landscape + pipApp.launchViaIntent(wmHelper) + // System bar may fade out during fixed rotation. + wmHelper + .StateSyncBuilder() + .withFullScreenApp(pipApp) + .withRotation(Rotation.ROTATION_90) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + } + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + // Launch the PiP activity fixed as landscape. + pipApp.launchViaIntent( wmHelper, stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) - ) - // Enter PiP. - broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP) - // System bar may fade out during fixed rotation. - wmHelper + mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + ) + // Enter PiP. + broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP) + // System bar may fade out during fixed rotation. + wmHelper .StateSyncBuilder() .withPipShown() .withRotation(Rotation.ROTATION_0) .withNavOrTaskBarVisible() .withStatusBarVisible() .waitForAndVerify() - } - teardown { pipApp.exit(wmHelper) } - transitions { - // Launch the activity back into fullscreen and ensure that it is now in landscape - pipApp.launchViaIntent(wmHelper) - // System bar may fade out during fixed rotation. - wmHelper - .StateSyncBuilder() - .withFullScreenApp(pipApp) - .withRotation(Rotation.ROTATION_90) - .withNavOrTaskBarVisible() - .withStatusBarVisible() - .waitForAndVerify() - } } + } /** * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index 703784dd8c67..43d6c8f26126 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -63,14 +63,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) - override val transition: FlickerBuilder.() -> Unit - get() = buildTransition { - setup { - testApp.launchViaIntent(wmHelper) - setRotation(flicker.scenario.startRotation) - } - transitions { setRotation(flicker.scenario.endRotation) } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + testApp.launchViaIntent(wmHelper) + setRotation(flicker.scenario.startRotation) } + transitions { setRotation(flicker.scenario.endRotation) } + } /** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index ada3455fae18..1dfdbf6514ba 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -29,6 +29,7 @@ import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; +import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; @@ -90,6 +91,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { private PipDisplayLayoutState mPipDisplayLayoutState; + private PipTouchState mPipTouchState; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -104,8 +107,12 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator); + + mPipTouchState = new PipTouchState(ViewConfiguration.get(mContext), + () -> {}, () -> {}, mMainExecutor); mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm, - mPipBoundsState, motionHelper, mPipTaskOrganizer, mPipDismissTargetHandler, + mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer, + mPipDismissTargetHandler, (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController, mMainExecutor) { @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 8d4aa9a7b25e..c967b568042c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -688,8 +688,12 @@ public class Utils { continue; } for (int complianceWarningType : complianceWarnings) { - if (complianceWarningType != 0) { - return true; + switch (complianceWarningType) { + case UsbPortStatus.COMPLIANCE_WARNING_OTHER: + case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY: + return true; + default: + break; } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index fe8988385453..6eb2f3834858 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -484,8 +484,9 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); synchronized (mEntriesMap) { AppEntry entry = null; - if (mEntriesMap.contains(userId)) { - entry = mEntriesMap.get(userId).get(packageName); + HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId); + if (userEntriesMap != null) { + entry = userEntriesMap.get(packageName); } if (entry == null) { ApplicationInfo info = getAppInfoLocked(packageName, userId); @@ -735,8 +736,9 @@ public class ApplicationsState { private AppEntry getEntryLocked(ApplicationInfo info) { int userId = UserHandle.getUserId(info.uid); AppEntry entry = null; - if (mEntriesMap.contains(userId)) { - entry = mEntriesMap.get(userId).get(info.packageName); + HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId); + if (userEntriesMap != null) { + entry = userEntriesMap.get(info.packageName); } if (DEBUG) { Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); @@ -752,8 +754,11 @@ public class ApplicationsState { Log.i(TAG, "Creating AppEntry for " + info.packageName); } entry = new AppEntry(mContext, info, mCurId++); - mEntriesMap.get(userId).put(info.packageName, entry); - mAppEntries.add(entry); + userEntriesMap = mEntriesMap.get(userId); + if (userEntriesMap != null) { + userEntriesMap.put(info.packageName, entry); + mAppEntries.add(entry); + } } else if (entry.info != info) { entry.info = info; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 0637e5d27f57..4a913c87bddf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -455,12 +455,24 @@ public class UtilsTest { } @Test - public void containsIncompatibleChargers_returnTrue() { - setupIncompatibleCharging(); + public void containsIncompatibleChargers_complianeWarningOther_returnTrue() { + setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER); + assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue(); + } + + @Test + public void containsIncompatibleChargers_complianeWarningDebug_returnTrue() { + setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY); assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue(); } @Test + public void containsIncompatibleChargers_unexpectedWarningType_returnFalse() { + setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_BC_1_2); + assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse(); + } + + @Test public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() { setupIncompatibleCharging(); when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]); @@ -494,12 +506,17 @@ public class UtilsTest { } private void setupIncompatibleCharging() { + setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER); + } + + private void setupIncompatibleCharging(int complianceWarningType) { final List<UsbPort> usbPorts = new ArrayList<>(); usbPorts.add(mUsbPort); when(mUsbManager.getPorts()).thenReturn(usbPorts); when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); when(mUsbPortStatus.isConnected()).thenReturn(true); - when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1}); + when(mUsbPortStatus.getComplianceWarnings()) + .thenReturn(new int[]{complianceWarningType}); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 1d081d7214cc..34d8148f418f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -804,7 +804,7 @@ public class ApplicationsStateRoboTest { } @Test - public void getEntry_validUserId_shouldReturnEntry() { + public void getEntry_hasCache_shouldReturnCacheEntry() { mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>()); addApp(PKG_1, /* id= */ 1); @@ -813,10 +813,13 @@ public class ApplicationsStateRoboTest { } @Test - public void getEntry_invalidUserId_shouldReturnNull() { - mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>()); - addApp(PKG_1, /* id= */ 1); + public void getEntry_hasNoCache_shouldReturnEntry() { + mApplicationsState.mEntriesMap.clear(); + ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0); + mApplicationsState.mApplications.add(appInfo); + mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false); - assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ -1)).isNull(); + assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName) + .isEqualTo(PKG_1); } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt index 83e44b69812b..7e1bfb921ca9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt @@ -18,6 +18,7 @@ package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis +import android.util.Log import android.util.LruCache import android.util.MathUtils import android.util.MathUtils.abs @@ -114,6 +115,9 @@ class FontInterpolator { tmpInterpKey.set(start, end, progress) val cachedFont = interpCache[tmpInterpKey] if (cachedFont != null) { + if (DEBUG) { + Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey") + } return cachedFont } @@ -159,6 +163,9 @@ class FontInterpolator { val axesCachedFont = verFontCache[tmpVarFontKey] if (axesCachedFont != null) { interpCache.put(InterpKey(start, end, progress), axesCachedFont) + if (DEBUG) { + Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey") + } return axesCachedFont } @@ -168,6 +175,9 @@ class FontInterpolator { val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() interpCache.put(InterpKey(start, end, progress), newFont) verFontCache.put(VarFontKey(start, newAxes), newFont) + if (DEBUG) { + Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") + } return newFont } @@ -233,6 +243,8 @@ class FontInterpolator { (v.coerceIn(min, max) / step).toInt() * step companion object { + private const val LOG_TAG = "FontInterpolator" + private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) private val EMPTY_AXES = arrayOf<FontVariationAxis>() // Returns true if given two font instance can be interpolated. diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 00108940e6ec..16ddf0c36d9d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -26,6 +26,7 @@ import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis import android.text.Layout import android.util.LruCache +import kotlin.math.roundToInt private const val DEFAULT_ANIMATION_DURATION: Long = 300 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 @@ -63,9 +64,9 @@ class TypefaceVariantCacheImpl( return it } - return TypefaceVariantCache - .createVariantTypeface(baseTypeface, fvar) - .also { cache.put(fvar, it) } + return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also { + cache.put(fvar, it) + } } } @@ -74,7 +75,6 @@ class TypefaceVariantCacheImpl( * * Currently this class can provide text style animation for text weight and text size. For example * the simple view that draws text with animating text size is like as follows: - * * <pre> <code> * ``` * class SimpleTextAnimation : View { @@ -97,6 +97,7 @@ class TypefaceVariantCacheImpl( */ class TextAnimator( layout: Layout, + numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps. private val invalidateCallback: () -> Unit, ) { var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface) @@ -112,7 +113,8 @@ class TextAnimator( ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION addUpdateListener { - textInterpolator.progress = it.animatedValue as Float + textInterpolator.progress = + calculateProgress(it.animatedValue as Float, numberOfAnimationSteps) invalidateCallback() } addListener( @@ -123,6 +125,17 @@ class TextAnimator( ) } + private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float { + if (numberOfAnimationSteps != null) { + // This clamps the progress to the nearest value of "numberOfAnimationSteps" + // discrete values between 0 and 1f. + return (animProgress * numberOfAnimationSteps).roundToInt() / + numberOfAnimationSteps.toFloat() + } + + return animProgress + } + sealed class PositionedGlyph { /** Mutable X coordinate of the glyph position relative from drawing offset. */ diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 465b73e6de19..648ef03895cd 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -74,7 +74,8 @@ class AnimatableClockView @JvmOverloads constructor( private var onTextAnimatorInitialized: Runnable? = null @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = - { layout, invalidateCb -> TextAnimator(layout, invalidateCb) } + { layout, invalidateCb -> + TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) } @VisibleForTesting var isAnimationEnabled: Boolean = true @VisibleForTesting var timeOverrideInMillis: Long? = null @@ -567,6 +568,7 @@ class AnimatableClockView @JvmOverloads constructor( private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500 private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000 private const val COLOR_ANIM_DURATION: Long = 400 + private const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30 // Constants for the animation private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 67fdb4c0c213..b48296fe54be 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -353,7 +353,7 @@ <!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]--> <string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> - <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face.</string> + <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> <string name="biometric_dialog_tap_confirm_with_face_alt_1">Unlocked by face. Press to continue.</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index f96d1e3d7fef..070a45170d04 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -62,6 +62,15 @@ open class ViewScreenshotTestRule( private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false override fun apply(base: Statement, description: Description): Statement { + if (isRobolectric) { + // In robolectric mode, we enable NATIVE graphics and unpack font and icu files. + // We need to use reflection, as this library is only needed and therefore + // only available in deviceless mode. + val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader" + val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName) + System.setProperty("robolectric.graphicsMode", "NATIVE") + defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null) + } val ruleToApply = if (isRobolectric) roboRule else delegateRule return ruleToApply.apply(base, description) } @@ -69,6 +78,7 @@ open class ViewScreenshotTestRule( protected fun takeScreenshot( mode: Mode = Mode.WrapContent, viewProvider: (ComponentActivity) -> View, + beforeScreenshot: (ComponentActivity) -> Unit = {} ): Bitmap { activityRule.scenario.onActivity { activity -> // Make sure that the activity draws full screen and fits the whole display instead of @@ -94,6 +104,7 @@ open class ViewScreenshotTestRule( val content = activity.requireViewById<ViewGroup>(android.R.id.content) assertEquals(1, content.childCount) contentView = content.getChildAt(0) + beforeScreenshot(activity) } return if (isRobolectric) { @@ -111,9 +122,10 @@ open class ViewScreenshotTestRule( fun screenshotTest( goldenIdentifier: String, mode: Mode = Mode.WrapContent, - viewProvider: (ComponentActivity) -> View, + beforeScreenshot: (ComponentActivity) -> Unit = {}, + viewProvider: (ComponentActivity) -> View ) { - val bitmap = takeScreenshot(mode, viewProvider) + val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot) screenshotRule.assertBitmapAgainstGolden( bitmap, goldenIdentifier, diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java index 3fce55f6e515..146d12c3e621 100644 --- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java +++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java @@ -29,13 +29,17 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemProperties; +import android.os.Trace; import android.view.Choreographer; +import android.view.View; +import android.view.ViewRootImpl; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.util.Assert; import java.util.ArrayList; import java.util.HashSet; +import java.util.Random; import java.util.Stack; import java.util.function.Supplier; @@ -43,12 +47,14 @@ import java.util.function.Supplier; * Utility class for methods used to dejank the UI. */ public class DejankUtils { + private static final String TRACK_NAME = "DejankUtils"; public static final boolean STRICT_MODE_ENABLED = Build.IS_ENG || SystemProperties.getBoolean("persist.sysui.strictmode", false); private static final Choreographer sChoreographer = Choreographer.getInstance(); private static final Handler sHandler = new Handler(); private static final ArrayList<Runnable> sPendingRunnables = new ArrayList<>(); + private static final Random sRandom = new Random(); private static Stack<String> sBlockingIpcs = new Stack<>(); private static boolean sTemporarilyIgnoreStrictMode; private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>(); @@ -254,4 +260,30 @@ public class DejankUtils { public static void setImmediate(boolean immediate) { sImmediate = immediate; } + + /** + * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks. + */ + public static void notifyRendererOfExpensiveFrame(View view, String reason) { + if (view == null) return; + notifyRendererOfExpensiveFrame(view.getViewRootImpl(), reason); + } + + /** + * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks. + */ + public static void notifyRendererOfExpensiveFrame(ViewRootImpl viewRoot, String reason) { + if (viewRoot == null) return; + if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) { + int cookie = sRandom.nextInt(); + Trace.asyncTraceForTrackBegin( + Trace.TRACE_TAG_APP, + TRACK_NAME, + "notifyRendererOfExpensiveFrame (" + reason + ")", + cookie); + DejankUtils.postAfterTraversal( + () -> Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, cookie)); + } + viewRoot.notifyRendererOfExpensiveFrame(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index c30a2146436c..8af92ce4bcb1 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -548,6 +548,10 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { if (!cancelled) { updateSwipeProgressFromOffset(animView, canBeDismissed); resetSwipeOfView(animView); + // Clear the snapped view after success, assuming it's not being swiped now + if (animView == mTouchedView && !mIsSwiping) { + mTouchedView = null; + } } onChildSnappedBack(animView, targetLeft); }); @@ -813,13 +817,39 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { } } - public void resetSwipeState() { - View swipedView = getSwipedView(); + private void resetSwipeState() { + resetSwipeStates(/* resetAll= */ false); + } + + public void resetTouchState() { + resetSwipeStates(/* resetAll= */ true); + } + + /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */ + private void resetSwipeStates(boolean resetAll) { + final View touchedView = mTouchedView; + final boolean wasSnapping = mSnappingChild; + final boolean wasSwiping = mIsSwiping; mTouchedView = null; mIsSwiping = false; - if (swipedView != null) { - snapChildIfNeeded(swipedView, false, 0); - onChildSnappedBack(swipedView, 0); + // If we were swiping, then we resetting swipe requires resetting everything. + resetAll |= wasSwiping; + if (resetAll) { + mSnappingChild = false; + } + if (touchedView == null) return; // No view to reset visually + // When snap needs to be reset, first thing is to cancel any translation animation + final boolean snapNeedsReset = resetAll && wasSnapping; + if (snapNeedsReset) { + cancelTranslateAnimation(touchedView); + } + // actually reset the view to default state + if (resetAll) { + snapChildIfNeeded(touchedView, false, 0); + } + // report if a swipe or snap was reset. + if (wasSwiping || snapNeedsReset) { + onChildSnappedBack(touchedView, 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 43a3b9958ee5..f86e2ed6eb91 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -638,12 +638,12 @@ public class AuthContainerView extends LinearLayout case Surface.ROTATION_90: mPanelController.setPosition(AuthPanelController.POSITION_RIGHT); - setScrollViewGravity(Gravity.BOTTOM | Gravity.RIGHT); + setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); break; case Surface.ROTATION_270: mPanelController.setPosition(AuthPanelController.POSITION_LEFT); - setScrollViewGravity(Gravity.BOTTOM | Gravity.LEFT); + setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); break; case Surface.ROTATION_180: diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java index acdde3404ab5..167067e7d7e9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java @@ -114,7 +114,13 @@ public class AuthPanelController extends ViewOutlineProvider { } private int getTopBound(@Position int position) { - return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); + switch (position) { + case POSITION_LEFT: + case POSITION_RIGHT: + return Math.max((mContainerHeight - mContentHeight) / 2, mMargin); + default: + return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin); + } } public void setContainerDimensions(int containerWidth, int containerHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3b40d8602e6f..b4871211be9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1943,7 +1943,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, startKeyguardExitAnimation(0, 0); } - mPowerGestureIntercepted = mUpdateMonitor.isSecureCameraLaunchedOverKeyguard(); + mPowerGestureIntercepted = + isOccluded && mUpdateMonitor.isSecureCameraLaunchedOverKeyguard(); if (mOccluded != isOccluded) { mOccluded = isOccluded; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt index c0a5a51a910d..4dc52ff46707 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt @@ -41,6 +41,8 @@ class BouncerMessageView : LinearLayout { super.onFinishInflate() primaryMessageView = findViewById(R.id.bouncer_primary_message_area) secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area) + primaryMessageView?.disable() + secondaryMessageView?.disable() } fun init(factory: KeyguardMessageAreaController.Factory) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index abf0932c8407..529b980a6e38 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -41,6 +41,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.graphics.drawable.IconCompat; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.qrcode.QrCodeGenerator; @@ -420,7 +421,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { return mMediaOutputController.getBroadcastMetadata(); } - private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) { + @VisibleForTesting + void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) { Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); if (positiveBtn != null) { positiveBtn.setEnabled(false); @@ -523,16 +525,33 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } private void handleUpdateFailedUi() { - final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); - mBroadcastErrorMessage.setVisibility(View.VISIBLE); + if (mAlertDialog == null) { + Log.d(TAG, "handleUpdateFailedUi: mAlertDialog is null"); + return; + } + int errorMessageStringId = -1; + boolean enablePositiveBtn = false; if (mRetryCount < MAX_BROADCAST_INFO_UPDATE) { - if (positiveBtn != null) { - positiveBtn.setEnabled(true); - } - mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error); + enablePositiveBtn = true; + errorMessageStringId = R.string.media_output_broadcast_update_error; } else { mRetryCount = 0; - mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error); + errorMessageStringId = R.string.media_output_broadcast_last_update_error; } + + // update UI + final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + if (positiveBtn != null && enablePositiveBtn) { + positiveBtn.setEnabled(true); + } + if (mBroadcastErrorMessage != null) { + mBroadcastErrorMessage.setVisibility(View.VISIBLE); + mBroadcastErrorMessage.setText(errorMessageStringId); + } + } + + @VisibleForTesting + int getRetryCount() { + return mRetryCount; } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 047fea123dc3..ca8369950e4b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -222,6 +222,8 @@ import com.android.systemui.util.Utils; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.animation.FlingAnimationUtils; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -232,7 +234,6 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; @CentralSurfacesComponent.CentralSurfacesScope @@ -3450,6 +3451,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void notifyExpandingStarted() { if (!mExpanding) { + DejankUtils.notifyRendererOfExpensiveFrame(mView, "notifyExpandingStarted"); mExpanding = true; mIsExpandingOrCollapsing = true; mQsController.onExpandingStarted(mQsController.getFullyExpanded()); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index c42c2f4fa15a..6480164cdaf5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -63,6 +63,7 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; import com.android.keyguard.FaceAuthApiRequestReason; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.classifier.Classifier; @@ -958,6 +959,7 @@ public class QuickSettingsController implements Dumpable { // TODO (b/265193930): remove dependency on NPVC mPanelViewControllerLazy.get().cancelHeightAnimator(); // end + DejankUtils.notifyRendererOfExpensiveFrame(mPanelView, "onExpansionStarted"); // Reset scroll position and apply that position to the expanded height. float height = mExpansionHeight; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index b6970aef6cad..39d213d46c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -83,7 +83,8 @@ open class BlurUtils @Inject constructor( return } if (lastAppliedBlur == 0 && radius != 0) { - Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, TRACK_NAME, EARLY_WAKEUP_SLICE_NAME, 0) + Trace.asyncTraceForTrackBegin( + TRACE_TAG_APP, TRACK_NAME, "eEarlyWakeup (prepareBlur)", 0) earlyWakeupEnabled = true createTransaction().use { it.setEarlyWakeupStart() @@ -110,7 +111,7 @@ open class BlurUtils @Inject constructor( Trace.asyncTraceForTrackBegin( TRACE_TAG_APP, TRACK_NAME, - EARLY_WAKEUP_SLICE_NAME, + "eEarlyWakeup (applyBlur)", 0 ) it.setEarlyWakeupStart() @@ -159,6 +160,5 @@ open class BlurUtils @Inject constructor( companion object { const val TRACK_NAME = "BlurUtils" - const val EARLY_WAKEUP_SLICE_NAME = "eEarlyWakeup" } } 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 bfb6fb0fcdc7..9f397fe9ac0c 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 @@ -556,6 +556,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void onNotificationUpdated() { + if (mIsSummaryWithChildren) { + Trace.beginSection("ExpNotRow#onNotifUpdated (summary)"); + } else { + Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)"); + } for (NotificationContentView l : mLayouts) { l.onNotificationUpdated(mEntry); } @@ -591,6 +596,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mUpdateSelfBackgroundOnUpdate = false; updateBackgroundColorsOfSelf(); } + Trace.endSection(); } private void updateBackgroundColorsOfSelf() { @@ -2588,6 +2594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsSummaryWithChildren = mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0; if (mIsSummaryWithChildren) { + Trace.beginSection("ExpNotRow#onChildCountChanged (summary)"); NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper(); if (wrapper == null || wrapper.getNotificationHeader() == null) { mChildrenContainer.recreateNotificationHeader(mExpandClickListener, @@ -2599,6 +2606,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateChildrenAppearance(); updateChildrenVisibility(); applyChildrenRoundness(); + if (mIsSummaryWithChildren) { + Trace.endSection(); + } } protected void expandNotification() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java index 77fd05186090..3e10f2ad52e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.content.res.Resources; +import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.TypedValue; import android.view.LayoutInflater; @@ -57,6 +58,7 @@ public class HybridGroupManager { } private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) { + Trace.beginSection("HybridGroupManager#inflateHybridView"); LayoutInflater inflater = LayoutInflater.from(mContext); int layout = contentView instanceof ConversationLayout ? R.layout.hybrid_conversation_notification @@ -64,6 +66,7 @@ public class HybridGroupManager { HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(layout, parent, false); parent.addView(hybrid); + Trace.endSection(); return hybrid; } @@ -90,12 +93,18 @@ public class HybridGroupManager { public HybridNotificationView bindFromNotification(HybridNotificationView reusableView, View contentView, StatusBarNotification notification, ViewGroup parent) { + boolean isNewView = false; if (reusableView == null) { + Trace.beginSection("HybridGroupManager#bindFromNotification"); reusableView = inflateHybridView(contentView, parent); + isNewView = true; } CharSequence titleText = resolveTitle(notification.getNotification()); CharSequence contentText = resolveText(notification.getNotification()); reusableView.bind(titleText, contentText, contentView); + if (isNewView) { + Trace.endSection(); + } return reusableView; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 451d837b63a0..124df8c3b815 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.RemoteException; +import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.AttributeSet; @@ -1193,6 +1194,7 @@ public class NotificationContentView extends FrameLayout implements Notification private void updateSingleLineView() { if (mIsChildInGroup) { + Trace.beginSection("NotifContentView#updateSingleLineView"); boolean isNewView = mSingleLineView == null; mSingleLineView = mHybridGroupManager.bindFromNotification( mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this); @@ -1200,6 +1202,7 @@ public class NotificationContentView extends FrameLayout implements Notification updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView); } + Trace.endSection(); } else if (mSingleLineView != null) { removeView(mSingleLineView); mSingleLineView = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index d18757d3453f..f8e374de11e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -379,6 +379,7 @@ public class NotificationChildrenContainer extends ViewGroup } public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) { + Trace.beginSection("NotifChildCont#recreateHeader"); mHeaderClickListener = listener; mIsConversation = isConversation; StatusBarNotification notification = mContainingNotification.getEntry().getSbn(); @@ -406,6 +407,7 @@ public class NotificationChildrenContainer extends ViewGroup recreateLowPriorityHeader(builder, isConversation); updateHeaderVisibility(false /* animate */); updateChildrenAppearance(); + Trace.endSection(); } /** 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 b81cb2be1c4d..f7c6594e3a70 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 @@ -4079,6 +4079,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (!mIsExpansionChanging) { cancelActiveSwipe(); } + finalizeClearAllAnimation(); } updateNotificationAnimationStates(); updateChronometers(); @@ -4174,20 +4175,30 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable runAnimationFinishedRunnables(); clearTransient(); clearHeadsUpDisappearRunning(); + finalizeClearAllAnimation(); + } + private void finalizeClearAllAnimation() { if (mAmbientState.isClearAllInProgress()) { setClearAllInProgress(false); if (mShadeNeedsToClose) { mShadeNeedsToClose = false; - postDelayed( - () -> { - mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); - }, - DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); + if (mIsExpanded) { + collapseShadeDelayed(); + } } } } + private void collapseShadeDelayed() { + postDelayed( + () -> { + mShadeController.animateCollapseShade( + CommandQueue.FLAG_EXCLUDE_NONE); + }, + DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); + } + private void clearHeadsUpDisappearRunning() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); @@ -4395,6 +4406,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll(); if (nowFullyHidden != wasFullyHidden) { updateVisibility(); + mSwipeHelper.resetTouchState(); } if (!wasHiddenAtAll && nowHiddenAtAll) { resetExposedMenuView(true /* animate */, true /* animate */); @@ -4535,6 +4547,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } + @VisibleForTesting public void setClearAllInProgress(boolean clearAllInProgress) { mClearAllInProgress = clearAllInProgress; mAmbientState.setClearAllInProgress(clearAllInProgress); @@ -4688,6 +4701,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); updateFooter(); + mController.updateImportantForAccessibility(); } updateSpeedBumpIndex(); @@ -4699,6 +4713,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); updateFooter(); + mController.updateImportantForAccessibility(); } updateSpeedBumpIndex(); @@ -4711,6 +4726,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); updateFooter(); + mController.updateImportantForAccessibility(); } updateSpeedBumpIndex(); @@ -5151,7 +5167,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mHeadsUpAppearanceController = headsUpAppearanceController; } - private boolean isVisible(View child) { + @VisibleForTesting + public boolean isVisible(View child) { boolean hasClipBounds = child.getClipBounds(mTmpRect); return child.getVisibility() == View.VISIBLE && (!hasClipBounds || mTmpRect.height() > 0); @@ -5850,7 +5867,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void cancelActiveSwipe() { - mSwipeHelper.resetSwipeState(); + mSwipeHelper.resetTouchState(); updateContinuousShadowDrawing(); } 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 7b046d6c9256..9272c376d4fe 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 @@ -327,6 +327,7 @@ public class NotificationStackScrollLayoutController { mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(), mLockscreenUserManager.isAnyProfilePublicMode()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); + updateImportantForAccessibility(); } }; @@ -1205,6 +1206,7 @@ public class NotificationStackScrollLayoutController { if (mView.getVisibility() == View.VISIBLE) { // Synchronize EmptyShadeView visibility with the parent container. updateShowEmptyShadeView(); + updateImportantForAccessibility(); } } @@ -1232,6 +1234,22 @@ public class NotificationStackScrollLayoutController { } /** + * Update the importantForAccessibility of NotificationStackScrollLayout. + * <p> + * We want the NSSL to be unimportant for accessibility when there's no + * notifications in it while the device is on lock screen, to avoid unlablel NSSL view. + * Otherwise, we want it to be important for accessibility to enable accessibility + * auto-scrolling in NSSL. + */ + public void updateImportantForAccessibility() { + if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { + mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } else { + mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + /** * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD * and false otherwise. */ 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 0402d4f88205..096f73f80165 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3677,7 +3677,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onShadeVisibilityChanged(boolean visible) { if (mVisible != visible) { mVisible = visible; - if (!visible) { + if (visible) { + DejankUtils.notifyRendererOfExpensiveFrame( + mNotificationShadeWindowView, "onShadeVisibilityChanged"); + } else { mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index d585163aa223..02b7e9176cf2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -21,7 +21,6 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD; import android.app.ActivityManager; -import android.app.Notification; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.ColorStateList; @@ -47,7 +46,6 @@ import android.view.OnReceiveContentListener; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; -import android.view.ViewGroupOverlay; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; @@ -58,6 +56,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -436,25 +435,23 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (animate && parent != null && mIsFocusAnimationFlagActive) { ViewGroup grandParent = (ViewGroup) parent.getParent(); - ViewGroupOverlay overlay = parent.getOverlay(); View actionsContainer = getActionsContainerLayout(); int actionsContainerHeight = actionsContainer != null ? actionsContainer.getHeight() : 0; - // After adding this RemoteInputView to the overlay of the parent (and thus removing - // it from the parent itself), the parent will shrink in height. This causes the - // overlay to be moved. To correct the position of the overlay we need to offset it. - int overlayOffsetY = actionsContainerHeight - getHeight(); - overlay.add(this); + // When defocusing, the notification needs to shrink. Therefore, we need to free + // up the space that was needed for the RemoteInputView. This is done by setting + // a negative top margin of the height difference of the RemoteInputView and its + // sibling (the actions_container_layout containing the Reply button etc.) + final int heightToShrink = actionsContainerHeight - getHeight(); + setTopMargin(heightToShrink); if (grandParent != null) grandParent.setClipChildren(false); - Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY); - View self = this; + final Animator animator = getDefocusAnimator(actionsContainer); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - overlay.remove(self); - parent.addView(self); + setTopMargin(0); if (grandParent != null) grandParent.setClipChildren(true); setVisibility(GONE); if (mWrapper != null) { @@ -499,6 +496,13 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } + private void setTopMargin(int topMargin) { + if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return; + final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams(); + layoutParams.topMargin = topMargin; + setLayoutParams(layoutParams); + } + @VisibleForTesting protected void setViewRootImpl(ViewRootImpl viewRoot) { mTestableViewRootImpl = viewRoot; @@ -674,12 +678,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mProgressBar.setVisibility(INVISIBLE); mResetting = true; mSending = false; + mController.removeSpinning(mEntry.getKey(), mToken); onDefocus(true /* animate */, false /* logClose */, () -> { mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); mEditText.getText().clear(); mEditText.setEnabled(isAggregatedVisible()); mSendButton.setVisibility(VISIBLE); - mController.removeSpinning(mEntry.getKey(), mToken); updateSendButton(); setAttachment(null); mResetting = false; @@ -874,7 +878,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f); scaleAnimator.addUpdateListener(valueAnimator -> { - setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0); + setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue()); }); scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION); scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN); @@ -901,9 +905,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene * Creates an animator for the defocus animation. * * @param fadeInView View that will be faded in during the defocus animation. - * @param offsetY The RemoteInputView will be offset by offsetY during the animation */ - private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) { + private Animator getDefocusAnimator(@Nullable View fadeInView) { final AnimatorSet animatorSet = new AnimatorSet(); final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f); @@ -913,14 +916,14 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE); scaleAnimator.addUpdateListener(valueAnimator -> { - setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY); + setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue()); }); scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION); scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN); scaleAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation, boolean isReverse) { - setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */); + setFocusAnimationScaleY(1f /* scaleY */); } }); @@ -942,9 +945,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene * Sets affected view properties for a vertical scale animation * * @param scaleY desired vertical view scale - * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation */ - private void setFocusAnimationScaleY(float scaleY, int verticalOffset) { + private void setFocusAnimationScaleY(float scaleY) { int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight()); Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(), mContentView.getHeight() - verticalBoundOffset); @@ -955,7 +957,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } else { mContentBackgroundBounds = contentBackgroundBounds; } - setTranslationY(verticalBoundOffset + verticalOffset); + setTranslationY(verticalBoundOffset); } /** Handler for button click on send action in IME. */ diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt index 8214822f0335..1e73cb3b9b24 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt @@ -1,6 +1,7 @@ package com.android.systemui.unfold import android.os.SystemProperties +import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator import com.android.systemui.dagger.qualifiers.Main @@ -22,6 +23,8 @@ constructor( ) : TransitionProgressListener { private var isFirstAnimationAfterUnfold = false + private val touchVibrationAttributes = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK) init { if (vibrator != null) { @@ -71,7 +74,7 @@ constructor( } private fun playHaptics() { - vibrator?.vibrate(effect) + vibrator?.vibrate(effect, touchVibrationAttributes) } private val hapticsScale: Float diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index 7236e0fd134a..59f2cdb745ca 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -224,7 +224,7 @@ object UserSwitcherViewBinder { } sectionView.removeAllViewsInLayout() - for (viewModel in section) { + section.onEachIndexed { index, viewModel -> val view = layoutInflater.inflate( R.layout.user_switcher_fullscreen_popup_item, @@ -237,6 +237,13 @@ object UserSwitcherViewBinder { view.resources.getString(viewModel.textResourceId) view.setOnClickListener { viewModel.onClicked() } sectionView.addView(view) + // Ensure that the first item in the first section gets accessibility focus. + // Request for focus with a delay when view is inflated an added to the listview. + if (index == 0 && position == 0) { + view.postDelayed({ + view.requestAccessibilityFocus() + }, 200) + } } return sectionView } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt index 14ad3acf7fb0..263d3750c657 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt @@ -26,18 +26,17 @@ import android.text.TextPaint import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat +import kotlin.math.ceil import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Mockito.`when` import org.mockito.Mockito.eq import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify - -import kotlin.math.ceil +import org.mockito.Mockito.`when` @RunWith(AndroidTestingRunner::class) @SmallTest @@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) - val textAnimator = TextAnimator(layout, {}).apply { - this.textInterpolator = textInterpolator - this.animator = valueAnimator - } + val textAnimator = + TextAnimator(layout, null, {}).apply { + this.textInterpolator = textInterpolator + this.animator = valueAnimator + } - textAnimator.setTextStyle( - weight = 400, - animate = true - ) + textAnimator.setTextStyle(weight = 400, animate = true) // If animation is requested, the base state should be rebased and the target state should // be updated. @@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) - val textAnimator = TextAnimator(layout, {}).apply { - this.textInterpolator = textInterpolator - this.animator = valueAnimator - } + val textAnimator = + TextAnimator(layout, null, {}).apply { + this.textInterpolator = textInterpolator + this.animator = valueAnimator + } - textAnimator.setTextStyle( - weight = 400, - animate = false - ) + textAnimator.setTextStyle(weight = 400, animate = false) // If animation is not requested, the progress should be 1 which is end of animation and the // base state is rebased to target state by calling rebase. @@ -118,15 +113,16 @@ class TextAnimatorTest : SysuiTestCase() { `when`(textInterpolator.targetPaint).thenReturn(paint) val animationEndCallback = mock(Runnable::class.java) - val textAnimator = TextAnimator(layout, {}).apply { - this.textInterpolator = textInterpolator - this.animator = valueAnimator - } + val textAnimator = + TextAnimator(layout, null, {}).apply { + this.textInterpolator = textInterpolator + this.animator = valueAnimator + } textAnimator.setTextStyle( - weight = 400, - animate = true, - onAnimationEnd = animationEndCallback + weight = 400, + animate = true, + onAnimationEnd = animationEndCallback ) // Verify animationEnd callback has been added. @@ -144,34 +140,27 @@ class TextAnimatorTest : SysuiTestCase() { val layout = makeLayout("Hello, World", PAINT) val valueAnimator = mock(ValueAnimator::class.java) val textInterpolator = mock(TextInterpolator::class.java) - val paint = TextPaint().apply { - typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") - } + val paint = + TextPaint().apply { + typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") + } `when`(textInterpolator.targetPaint).thenReturn(paint) - val textAnimator = TextAnimator(layout, {}).apply { - this.textInterpolator = textInterpolator - this.animator = valueAnimator - } + val textAnimator = + TextAnimator(layout, null, {}).apply { + this.textInterpolator = textInterpolator + this.animator = valueAnimator + } - textAnimator.setTextStyle( - weight = 400, - animate = true - ) + textAnimator.setTextStyle(weight = 400, animate = true) val prevTypeface = paint.typeface - textAnimator.setTextStyle( - weight = 700, - animate = true - ) + textAnimator.setTextStyle(weight = 700, animate = true) assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface) - textAnimator.setTextStyle( - weight = 400, - animate = true - ) + textAnimator.setTextStyle(weight = 400, animate = true) assertThat(paint.typeface).isSameInstanceAs(prevTypeface) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 705b485ce1b4..205fa0f11a26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dialog; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.mock; @@ -33,6 +35,8 @@ import android.media.session.MediaSessionManager; import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.widget.ImageView; +import android.widget.TextView; import androidx.test.filters.SmallTest; @@ -44,6 +48,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; @@ -58,6 +63,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -68,6 +74,9 @@ import java.util.Optional; public class MediaOutputBroadcastDialogTest extends SysuiTestCase { private static final String TEST_PACKAGE = "test_package"; + private static final String BROADCAST_NAME_TEST = "Broadcast_name_test"; + private static final String BROADCAST_CODE_TEST = "112233"; + private static final String BROADCAST_CODE_UPDATE_TEST = "11223344"; // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); @@ -106,6 +115,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null); + when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST); + when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn( + BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8)); mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -194,4 +206,50 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean()); } + + @Test + public void handleLeBroadcastMetadataChanged_checkBroadcastName() { + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + final TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_name_summary); + + mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged(); + + assertThat(broadcastName.getText().toString()).isEqualTo(BROADCAST_NAME_TEST); + } + + @Test + public void handleLeBroadcastMetadataChanged_checkBroadcastCode() { + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( + mLocalBluetoothLeBroadcast); + + final TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_code_summary); + + mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged(); + + assertThat(broadcastCode.getText().toString()).isEqualTo(BROADCAST_CODE_TEST); + } + + @Test + public void updateBroadcastInfo_stopBroadcastFailed_handleFailedUi() { + ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView + .requireViewById(R.id.broadcast_code_edit); + TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById( + R.id.broadcast_code_summary); + broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST); + when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); + broadcastCodeEdit.callOnClick(); + + mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST); + assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(1); + + mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST); + assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(2); + + // It will be the MAX Retry Count = 3 + mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST); + assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0); + } } 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 6a0e3c6d51eb..02666e40b98d 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 @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.metrics.LogMaker; import android.testing.AndroidTestingRunner; +import android.view.View; import androidx.test.filters.SmallTest; @@ -430,6 +431,84 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { verify(mNotificationStackScrollLayout).setStatusBarState(KEYGUARD); } + @Test + public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() { + // GIVEN: Controller is attached, active notifications is empty, + // and mNotificationStackScrollLayout.onKeyguard() is true + initController(/* viewIsAttached= */ true); + when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); + mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); + + // WHEN: call updateImportantForAccessibility + mController.updateImportantForAccessibility(); + + // THEN: mNotificationStackScrollLayout should not be important for A11y + verify(mNotificationStackScrollLayout) + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + @Test + public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() { + // GIVEN: Controller is attached, active notifications is not empty, + // and mNotificationStackScrollLayout.onKeyguard() is true + initController(/* viewIsAttached= */ true); + when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); + mController.getNotifStackController().setNotifStats( + new NotifStats( + /* numActiveNotifs = */ 1, + /* hasNonClearableAlertingNotifs = */ false, + /* hasClearableAlertingNotifs = */ false, + /* hasNonClearableSilentNotifs = */ false, + /* hasClearableSilentNotifs = */ false) + ); + + // WHEN: call updateImportantForAccessibility + mController.updateImportantForAccessibility(); + + // THEN: mNotificationStackScrollLayout should be important for A11y + verify(mNotificationStackScrollLayout) + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + + @Test + public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() { + // GIVEN: Controller is attached, active notifications is not empty, + // and mNotificationStackScrollLayout.onKeyguard() is false + initController(/* viewIsAttached= */ true); + when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); + mController.getNotifStackController().setNotifStats( + new NotifStats( + /* numActiveNotifs = */ 1, + /* hasNonClearableAlertingNotifs = */ false, + /* hasClearableAlertingNotifs = */ false, + /* hasNonClearableSilentNotifs = */ false, + /* hasClearableSilentNotifs = */ false) + ); + + // WHEN: call updateImportantForAccessibility + mController.updateImportantForAccessibility(); + + // THEN: mNotificationStackScrollLayout should be important for A11y + verify(mNotificationStackScrollLayout) + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + + @Test + public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() { + // GIVEN: Controller is attached, active notifications is empty, + // and mNotificationStackScrollLayout.onKeyguard() is false + initController(/* viewIsAttached= */ true); + when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); + mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); + + // WHEN: call updateImportantForAccessibility + mController.updateImportantForAccessibility(); + + // THEN: mNotificationStackScrollLayout should be important for A11y + verify(mNotificationStackScrollLayout) + .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + private LogMaker logMatcher(int category, int type) { return argThat(new LogMatcher(category, type)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a4ee349f5b71..85b1ec108e98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -575,10 +576,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.inflateFooterView(); // add notification - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - NotificationEntry entry = mock(NotificationEntry.class); - when(row.getEntry()).thenReturn(entry); - when(entry.isClearable()).thenReturn(true); + ExpandableNotificationRow row = createClearableRow(); mStackScroller.addContainerView(row); mStackScroller.onUpdateRowStates(); @@ -648,6 +646,50 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testClearNotifications_clearAllInProgress() { + ExpandableNotificationRow row = createClearableRow(); + when(row.getEntry().hasFinishedInitialization()).thenReturn(true); + doReturn(true).when(mStackScroller).isVisible(row); + mStackScroller.addContainerView(row); + + mStackScroller.clearNotifications(ROWS_ALL, false); + + assertClearAllInProgress(true); + verify(mNotificationRoundnessManager).setClearAllInProgress(true); + } + + @Test + public void testOnChildAnimationFinished_resetsClearAllInProgress() { + mStackScroller.setClearAllInProgress(true); + + mStackScroller.onChildAnimationFinished(); + + assertClearAllInProgress(false); + verify(mNotificationRoundnessManager).setClearAllInProgress(false); + } + + @Test + public void testShadeCollapsed_resetsClearAllInProgress() { + mStackScroller.setClearAllInProgress(true); + + mStackScroller.setIsExpanded(false); + + assertClearAllInProgress(false); + verify(mNotificationRoundnessManager).setClearAllInProgress(false); + } + + @Test + public void testShadeExpanded_doesntChangeClearAllInProgress() { + mStackScroller.setClearAllInProgress(true); + clearInvocations(mNotificationRoundnessManager); + + mStackScroller.setIsExpanded(true); + + assertClearAllInProgress(true); + verify(mNotificationRoundnessManager, never()).setClearAllInProgress(anyBoolean()); + } + + @Test public void testAddNotificationUpdatesSpeedBumpIndex() { // initial state calculated == 0 assertEquals(0, mStackScroller.getSpeedBumpIndex()); @@ -794,7 +836,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - public void onShadeClosesWithAnimationWillResetSwipeState() { + public void onShadeClosesWithAnimationWillResetTouchState() { // GIVEN shade is expanded mStackScroller.setIsExpanded(true); clearInvocations(mNotificationSwipeHelper); @@ -804,12 +846,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.setIsExpanded(false); mStackScroller.onExpansionStopped(); - // VERIFY swipe is reset - verify(mNotificationSwipeHelper).resetSwipeState(); + // VERIFY touch is reset + verify(mNotificationSwipeHelper).resetTouchState(); } @Test - public void onShadeClosesWithoutAnimationWillResetSwipeState() { + public void onShadeClosesWithoutAnimationWillResetTouchState() { // GIVEN shade is expanded mStackScroller.setIsExpanded(true); clearInvocations(mNotificationSwipeHelper); @@ -817,8 +859,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // WHEN closing the shade without the animation mStackScroller.setIsExpanded(false); - // VERIFY swipe is reset - verify(mNotificationSwipeHelper).resetSwipeState(); + // VERIFY touch is reset + verify(mNotificationSwipeHelper).resetTouchState(); } @Test @@ -896,6 +938,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mStackScroller.setStatusBarState(state); } + private ExpandableNotificationRow createClearableRow() { + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationEntry entry = mock(NotificationEntry.class); + when(row.canViewBeCleared()).thenReturn(true); + when(row.getEntry()).thenReturn(entry); + when(entry.isClearable()).thenReturn(true); + + return row; + } + + private void assertClearAllInProgress(boolean expected) { + assertEquals(expected, mStackScroller.getClearAllInProgress()); + assertEquals(expected, mAmbientState.isClearAllInProgress()); + } + private static void mockBoundsOnScreen(View view, Rect bounds) { doAnswer(invocation -> { Rect out = invocation.getArgument(0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt index 3dec45b4ff9f..b9c7e6133669 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.unfold +import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator import android.testing.AndroidTestingRunner @@ -53,7 +54,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() { progressProvider.onTransitionProgress(0.5f) progressProvider.onTransitionFinishing() - verify(vibrator).vibrate(any<VibrationEffect>()) + verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>()) } @Test @@ -64,7 +65,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() { progressProvider.onTransitionProgress(0.99f) progressProvider.onTransitionFinishing() - verify(vibrator, never()).vibrate(any<VibrationEffect>()) + verify(vibrator, never()).vibrate(any<VibrationEffect>(), any<VibrationAttributes>()) } @Test @@ -84,7 +85,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() { progressProvider.onTransitionFinished() testFoldProvider.onFoldUpdate(isFolded = true) - verify(vibrator, never()).vibrate(any<VibrationEffect>()) + verify(vibrator, never()).vibrate(any<VibrationEffect>(), any<VibrationAttributes>()) } @Test @@ -112,6 +113,6 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() { progressProvider.onTransitionFinishing() progressProvider.onTransitionFinished() - verify(vibrator).vibrate(any<VibrationEffect>()) + verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>()) } } diff --git a/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java new file mode 100644 index 000000000000..b4aca1530204 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java @@ -0,0 +1,79 @@ +/* + * 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.autofill; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.RemoteCallback; +import android.service.autofill.FieldClassification; +import android.service.autofill.FillEventHistory.Event.NoSaveReason; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager.AutofillCommitReason; + +import java.util.ArrayList; + +class LogFieldClassificationScoreOnResultListener implements + RemoteCallback.OnResultListener { + + private static final String TAG = "LogFieldClassificationScoreOnResultListener"; + + private Session mSession; + private final @NoSaveReason int mSaveDialogNotShowReason; + private final @AutofillCommitReason int mCommitReason; + private final int mViewsSize; + private final AutofillId[] mAutofillIds; + private final String[] mUserValues; + private final String[] mCategoryIds; + private final ArrayList<AutofillId> mDetectedFieldIds; + private final ArrayList<FieldClassification> mDetectedFieldClassifications; + LogFieldClassificationScoreOnResultListener(Session session, + int saveDialogNotShowReason, + int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, + String[] categoryIds, ArrayList<AutofillId> detectedFieldIds, + ArrayList<FieldClassification> detectedFieldClassifications) { + this.mSession = session; + this.mSaveDialogNotShowReason = saveDialogNotShowReason; + this.mCommitReason = commitReason; + this.mViewsSize = viewsSize; + this.mAutofillIds = autofillIds; + this.mUserValues = userValues; + this.mCategoryIds = categoryIds; + this.mDetectedFieldIds = detectedFieldIds; + this.mDetectedFieldClassifications = detectedFieldClassifications; + } + + public void onResult(@Nullable Bundle result) { + // Create a local copy to safe guard race condition + Session session = mSession; + if (session == null) { + Slog.wtf(TAG, "session is null when calling onResult()"); + return; + } + session.handleLogFieldClassificationScore( + result, + mSaveDialogNotShowReason, + mCommitReason, + mViewsSize, + mAutofillIds, + mUserValues, + mCategoryIds, + mDetectedFieldIds, + mDetectedFieldClassifications); + mSession = null; + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 563e431b5737..0c3f8667f4f8 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3063,76 +3063,91 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Then use the results, asynchronously - final RemoteCallback callback = new RemoteCallback((result) -> { - if (result == null) { - if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); - logContextCommitted(null, null, saveDialogNotShowReason, commitReason); - return; - } - final Scores scores = result.getParcelable(EXTRA_SCORES, android.service.autofill.AutofillFieldClassificationService.Scores.class); - if (scores == null) { - Slog.w(TAG, "No field classification score on " + result); - return; - } - int i = 0, j = 0; - try { - // Iteract over all autofill fields first - for (i = 0; i < viewsSize; i++) { - final AutofillId autofillId = autofillIds[i]; - - // Search the best scores for each category (as some categories could have - // multiple user values - ArrayMap<String, Float> scoresByField = null; - for (j = 0; j < userValues.length; j++) { - final String categoryId = categoryIds[j]; - final float score = scores.scores[i][j]; - if (score > 0) { - if (scoresByField == null) { - scoresByField = new ArrayMap<>(userValues.length); - } - final Float currentScore = scoresByField.get(categoryId); - if (currentScore != null && currentScore > score) { - if (sVerbose) { - Slog.v(TAG, "skipping score " + score - + " because it's less than " + currentScore); - } - continue; - } + final RemoteCallback callback = new RemoteCallback( + new LogFieldClassificationScoreOnResultListener( + this, + saveDialogNotShowReason, + commitReason, + viewsSize, + autofillIds, + userValues, + categoryIds, + detectedFieldIds, + detectedFieldClassifications)); + + fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, + defaultAlgorithm, defaultArgs, algorithms, args); + } + + void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason, + int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, + String[] categoryIds, ArrayList<AutofillId> detectedFieldIds, + ArrayList<FieldClassification> detectedFieldClassifications) { + if (result == null) { + if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); + logContextCommitted(null, null, saveDialogNotShowReason, commitReason); + return; + } + final Scores scores = result.getParcelable(EXTRA_SCORES, + android.service.autofill.AutofillFieldClassificationService.Scores.class); + if (scores == null) { + Slog.w(TAG, "No field classification score on " + result); + return; + } + int i = 0, j = 0; + try { + // Iteract over all autofill fields first + for (i = 0; i < viewsSize; i++) { + final AutofillId autofillId = autofillIds[i]; + + // Search the best scores for each category (as some categories could have + // multiple user values + ArrayMap<String, Float> scoresByField = null; + for (j = 0; j < userValues.length; j++) { + final String categoryId = categoryIds[j]; + final float score = scores.scores[i][j]; + if (score > 0) { + if (scoresByField == null) { + scoresByField = new ArrayMap<>(userValues.length); + } + final Float currentScore = scoresByField.get(categoryId); + if (currentScore != null && currentScore > score) { if (sVerbose) { - Slog.v(TAG, "adding score " + score + " at index " + j + " and id " - + autofillId); + Slog.v(TAG, "skipping score " + score + + " because it's less than " + currentScore); } - scoresByField.put(categoryId, score); - } else if (sVerbose) { - Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); + continue; } + if (sVerbose) { + Slog.v(TAG, "adding score " + score + " at index " + j + " and id " + + autofillId); + } + scoresByField.put(categoryId, score); + } else if (sVerbose) { + Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId); } - if (scoresByField == null) { - if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); - continue; - } - - // Then create the matches for that autofill id - final ArrayList<Match> matches = new ArrayList<>(scoresByField.size()); - for (j = 0; j < scoresByField.size(); j++) { - final String fieldId = scoresByField.keyAt(j); - final float score = scoresByField.valueAt(j); - matches.add(new Match(fieldId, score)); - } - detectedFieldIds.add(autofillId); - detectedFieldClassifications.add(new FieldClassification(matches)); - } // for i - } catch (ArrayIndexOutOfBoundsException e) { - wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); - return; - } - - logContextCommitted(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); - }); + } + if (scoresByField == null) { + if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId); + continue; + } - fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, - defaultAlgorithm, defaultArgs, algorithms, args); + // Then create the matches for that autofill id + final ArrayList<Match> matches = new ArrayList<>(scoresByField.size()); + for (j = 0; j < scoresByField.size(); j++) { + final String fieldId = scoresByField.keyAt(j); + final float score = scoresByField.valueAt(j); + matches.add(new Match(fieldId, score)); + } + detectedFieldIds.add(autofillId); + detectedFieldClassifications.add(new FieldClassification(matches)); + } // for i + } catch (ArrayIndexOutOfBoundsException e) { + wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); + return; + } + logContextCommitted(detectedFieldIds, detectedFieldClassifications, + saveDialogNotShowReason, commitReason); } /** diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index 80406e6f66e4..38e7371e7075 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -113,6 +113,11 @@ public class ForegroundServiceTypeLoggerModule { // We use this to get the duration an API was active after // the stop call. final SparseArray<Long> mLastFgsTimeStamp = new SparseArray<>(); + + // A map of API types to first FGS start call timestamps + // We use this to get the duration an API was active after + // the stop call. + final SparseArray<Long> mFirstFgsTimeStamp = new SparseArray<>(); } // SparseArray that tracks all UIDs that have made various @@ -146,6 +151,7 @@ public class ForegroundServiceTypeLoggerModule { if (fgsIndex < 0) { uidState.mRunningFgs.put(apiType, new ArrayMap<>()); fgsIndex = uidState.mRunningFgs.indexOfKey(apiType); + uidState.mFirstFgsTimeStamp.put(apiType, System.currentTimeMillis()); } final ArrayMap<ComponentName, ServiceRecord> fgsList = uidState.mRunningFgs.valueAt(fgsIndex); @@ -237,7 +243,7 @@ public class ForegroundServiceTypeLoggerModule { // there's no more FGS running for this type, just get rid of it uidState.mRunningFgs.remove(apiType); // but we need to keep track of the timestamp in case an API stops - uidState.mLastFgsTimeStamp.put(apiType, record.mFgsExitTime); + uidState.mLastFgsTimeStamp.put(apiType, System.currentTimeMillis()); } } if (!apisFound.isEmpty()) { @@ -454,8 +460,17 @@ public class ForegroundServiceTypeLoggerModule { public void logFgsApiEvent(ServiceRecord r, int fgsState, @FgsApiState int apiState, @ForegroundServiceApiType int apiType, long timestamp) { - final long apiDurationBeforeFgsStart = r.mFgsEnterTime - timestamp; - final long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime; + long apiDurationBeforeFgsStart = r.createRealTime - timestamp; + long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime; + UidState uidState = mUids.get(r.appInfo.uid); + if (uidState != null) { + if (uidState.mFirstFgsTimeStamp.contains(apiType)) { + apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp; + } + if (uidState.mLastFgsTimeStamp.contains(apiType)) { + apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType); + } + } final int[] apiTypes = new int[1]; apiTypes[0] = apiType; final long[] timeStamps = new long[1]; diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index 7841b699ec98..ffe5a6e6b958 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import android.annotation.UptimeMillisLong; import android.app.ActivityManagerInternal.OomAdjReason; +import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -320,5 +321,8 @@ final class ProcessCachedOptimizerRecord { pw.print(prefix); pw.print("isFreezeExempt="); pw.print(mFreezeExempt); pw.print(" isPendingFreeze="); pw.print(mPendingFreeze); pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen); + pw.print(prefix); pw.print("earliestFreezableTimeMs="); + TimeUtils.formatDuration(mEarliestFreezableTimeMillis, nowUptime, pw); + pw.println(); } } diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index a2582083c409..a6677a5185ca 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -429,21 +429,23 @@ public class UidObserverController { } } - pw.println(); - pw.print(" mUidChangeDispatchCount="); - pw.print(mUidChangeDispatchCount); - pw.println(); - pw.println(" Slow UID dispatches:"); - for (int i = 0; i < count; i++) { - final UidObserverRegistration reg = (UidObserverRegistration) - mUidObservers.getRegisteredCallbackCookie(i); - pw.print(" "); - pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName()); - pw.print(": "); - pw.print(reg.mSlowDispatchCount); - pw.print(" / Max "); - pw.print(reg.mMaxDispatchTime); - pw.println("ms"); + if (dumpPackage == null) { + pw.println(); + pw.print(" mUidChangeDispatchCount="); + pw.print(mUidChangeDispatchCount); + pw.println(); + pw.println(" Slow UID dispatches:"); + for (int i = 0; i < count; i++) { + final UidObserverRegistration reg = (UidObserverRegistration) + mUidObservers.getRegisteredCallbackCookie(i); + pw.print(" "); + pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName()); + pw.print(": "); + pw.print(reg.mSlowDispatchCount); + pw.print(" / Max "); + pw.print(reg.mMaxDispatchTime); + pw.println("ms"); + } } } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index b86da88d15c3..1f8ff73b2d5d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -2210,8 +2210,8 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.setPreferredDevicesForStrategyInt( mAccessibilityStrategyId, Arrays.asList(defaultDevice)); } else { - mDeviceInventory.removePreferredDevicesForStrategInt(mCommunicationStrategyId); - mDeviceInventory.removePreferredDevicesForStrategInt(mAccessibilityStrategyId); + mDeviceInventory.removePreferredDevicesForStrategyInt(mCommunicationStrategyId); + mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId); } mDeviceInventory.applyConnectedDevicesRoles(); } else { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 7b2e732702a3..d1cae490a31d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -823,7 +823,7 @@ public class AudioDeviceInventory { return status; } // Only used for internal requests - /*package*/ int removePreferredDevicesForStrategInt(int strategy) { + /*package*/ int removePreferredDevicesForStrategyInt(int strategy) { return clearDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 44ae454e7ef2..c212e8e3c82c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -19,6 +19,7 @@ package com.android.server.inputmethod; import static com.android.server.inputmethod.InputMethodManagerService.DEBUG; import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; +import android.annotation.Nullable; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -65,8 +66,8 @@ final class InputMethodMenuController { private boolean mShowImeWithHardKeyboard; @GuardedBy("ImfLock.class") - private final InputMethodDialogWindowContext mDialogWindowContext = - new InputMethodDialogWindowContext(); + @Nullable + private InputMethodDialogWindowContext mDialogWindowContext; InputMethodMenuController(InputMethodManagerService service) { mService = service; @@ -124,11 +125,13 @@ final class InputMethodMenuController { } } + if (mDialogWindowContext == null) { + mDialogWindowContext = new InputMethodDialogWindowContext(); + } final Context dialogWindowContext = mDialogWindowContext.get(displayId); mDialogBuilder = new AlertDialog.Builder(dialogWindowContext); mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu()); - // TODO(b/277061090): refactor UI components should not be created while holding a lock. final Context dialogContext = mDialogBuilder.getContext(); final TypedArray a = dialogContext.obtainStyledAttributes(null, com.android.internal.R.styleable.DialogPreference, @@ -196,11 +199,10 @@ final class InputMethodMenuController { attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; attrs.setTitle("Select input method"); w.setAttributes(attrs); - // TODO(b/277062834) decouple/remove dependency on IMMS mService.updateSystemUiLocked(); mService.sendOnNavButtonFlagsChangedLocked(); + mSwitchingDialog.show(); } - mSwitchingDialog.show(); } private boolean isScreenLocked() { @@ -274,7 +276,6 @@ final class InputMethodMenuController { private final int mTextViewResourceId; private final List<ImeSubtypeListItem> mItemsList; public int mCheckedItem; - private ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem) { super(context, textViewResourceId, itemsList); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e56eba659d37..aeff2b0f9af6 100755..100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1843,7 +1843,7 @@ public class NotificationManagerService extends SystemService { } } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userHandle >= 0) { + if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle, REASON_PROFILE_TURNED_OFF, null); mSnoozeHelper.clearData(userHandle); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bba8043af5be..db47306ad58e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3409,7 +3409,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Nullable - private String getDevicePolicyManagementRoleHolderPackageName(int userId) { + public String getDevicePolicyManagementRoleHolderPackageName(int userId) { return Binder.withCleanCallingIdentity(() -> { RoleManager roleManager = mContext.getSystemService(RoleManager.class); List<String> roleHolders = diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index ba825774daf6..08934c69e099 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -724,6 +724,10 @@ public final class SuspendPackageHelper { for (PackageInfo info : pkgInfos) { result.add(info.packageName); } + + // Role holder may be null, but ArraySet handles it correctly. + result.remove(mPm.getDevicePolicyManagementRoleHolderPackageName(userId)); + return result; } diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 90af4c6236aa..1eb56f1b7d1c 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -280,6 +280,10 @@ class ActivityStartInterceptor { return false; } + if (isKeepProfilesRunningEnabled() && !isPackageSuspended()) { + return false; + } + IntentSender target = createIntentSenderForOriginalIntent(mCallingUid, FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT); @@ -322,8 +326,7 @@ class ActivityStartInterceptor { private boolean interceptSuspendedPackageIfNeeded() { // Do not intercept if the package is not suspended - if (mAInfo == null || mAInfo.applicationInfo == null || - (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) { + if (!isPackageSuspended()) { return false; } final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked(); @@ -467,6 +470,17 @@ class ActivityStartInterceptor { return true; } + private boolean isPackageSuspended() { + return mAInfo != null && mAInfo.applicationInfo != null + && (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0; + } + + private static boolean isKeepProfilesRunningEnabled() { + DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + return dpmi == null || dpmi.isKeepProfilesRunningEnabled(); + } + /** * Called when an activity is successfully launched. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index de1403b64477..a5b15489f292 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -49,6 +49,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY; @@ -73,7 +74,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER; -import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI; @@ -13721,7 +13721,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); + UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( @@ -13811,13 +13811,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); + UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS}); + UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER}); USER_RESTRICTION_PERMISSIONS.put( @@ -13843,7 +13843,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS}); + UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( @@ -23021,6 +23021,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_MICROPHONE, MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_MODIFY_USERS, MANAGE_DEVICE_POLICY_MTE, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, @@ -23044,7 +23045,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_TIME, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_USERS, MANAGE_DEVICE_POLICY_VPN, MANAGE_DEVICE_POLICY_WALLPAPER, MANAGE_DEVICE_POLICY_WIFI, @@ -23065,12 +23065,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_TASK, + MANAGE_DEVICE_POLICY_MODIFY_USERS, MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, MANAGE_DEVICE_POLICY_SAFE_BOOT, MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_USERS, MANAGE_DEVICE_POLICY_WIPE_DATA ); @@ -23156,6 +23156,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_FUN, MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_MODIFY_USERS, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PRINTING, MANAGE_DEVICE_POLICY_PROFILES, @@ -23164,7 +23165,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_SMS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_USERS, MANAGE_DEVICE_POLICY_WINDOWS, SET_TIME, SET_TIME_ZONE @@ -23354,6 +23354,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, @@ -23374,8 +23376,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER, diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp index 1146271368af..3176f0673b65 100644 --- a/services/tests/PackageManagerServiceTests/server/Android.bp +++ b/services/tests/PackageManagerServiceTests/server/Android.bp @@ -106,6 +106,10 @@ android_test { ":PackageParserTestApp6", ], resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"], + + data: [ + ":StubTestApp", + ], } // Rules to copy all the test apks to the intermediate raw resource directory diff --git a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml index 1b93527065ec..a0ef03c69e3a 100644 --- a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml +++ b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml @@ -23,6 +23,14 @@ <option name="install-arg" value="-g" /> <option name="test-file-name" value="PackageManagerServiceServerTests.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put global verifier_engprod 1" /> + </target_preparer> + + <!-- Load additional APKs onto device --> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/> + </target_preparer> <option name="test-tag" value="PackageManagerServiceServerTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java index b82ffb4d0b39..435f0d7f5f15 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java @@ -32,7 +32,6 @@ import android.app.AppGlobals; import android.content.IIntentReceiver; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -70,7 +69,7 @@ import java.util.regex.Pattern; @RunWith(AndroidJUnit4.class) public class PackageManagerServiceTest { - private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; + private static final String PACKAGE_NAME = "com.android.server.pm.test.service.server"; private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/"; private static final String TEST_APP_APK = "StubTestApp.apk"; diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt index cfef0b23b3e7..5fd270ecb2b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt @@ -48,6 +48,7 @@ open class PackageHelperTestBase { const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller" const val VERIFIER_PACKAGE = "com.android.test.known.verifier" const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission" + const val MGMT_ROLE_HOLDER_PACKAGE = "com.android.test.know.device_management" const val TEST_USER_ID = 0 } @@ -119,6 +120,8 @@ open class PackageHelperTestBase { Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms) .getKnownPackageNamesInternal(any(), eq(KnownPackages.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID)) + Mockito.doReturn(MGMT_ROLE_HOLDER_PACKAGE).`when`(pms) + .getDevicePolicyManagementRoleHolderPackageName(eq(TEST_USER_ID)) } private fun createPackageManagerService(vararg stageExistingPackages: String): diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index f9a8ead9cd4a..5cca5fa8ea0b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -128,13 +128,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { fun setPackagesSuspended_forQuietMode() { val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, - PERMISSION_CONTROLLER_PACKAGE) + PERMISSION_CONTROLLER_PACKAGE, MGMT_ROLE_HOLDER_PACKAGE) val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), knownPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, true /* forQuietMode */)!! - assertThat(failedNames.size).isEqualTo(0) + assertThat(failedNames.size).isEqualTo(1) + assertThat(failedNames[0]).isEqualTo(MGMT_ROLE_HOLDER_PACKAGE) } @Test diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index cfeaf0b54552..16e1b457ad96 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -114,7 +114,6 @@ android_test { ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", ":SimpleServiceTestApp3", - ":StubTestApp", ":SuspendTestApp", ":MediaButtonReceiverHolderTestHelperApp", "data/broken_shortcut.xml", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b304968f3e69..fbb0ca108ecd 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -44,11 +44,6 @@ <option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/> </target_preparer> - <!-- Load additional APKs onto device --> - <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> - <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/> - </target_preparer> - <option name="test-tag" value="FrameworksServicesTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.servicestests" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index b0fd64976c48..c98de7c4d00e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -66,6 +66,7 @@ import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; @@ -307,6 +308,7 @@ public class WindowMagnificationManagerTest { MagnificationScaleProvider.MAX_SCALE); } + @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.") @Test public void logTrackingTypingFocus_processScroll_logDuration() { WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager); diff --git a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java index b46f1a61c745..50ea48719948 100644 --- a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java +++ b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java @@ -15,14 +15,27 @@ */ package com.android.server.credentials; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.credentials.metrics.BrowsedAuthenticationMetric; +import com.android.server.credentials.metrics.CandidateAggregateMetric; +import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; +import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric; import com.android.server.credentials.metrics.InitialPhaseMetric; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Given the secondary-system nature of the MetricUtilities, we expect absolutely nothing to * throw an error. If one presents itself, that is problematic. @@ -33,6 +46,11 @@ import org.junit.runner.RunWith; @SmallTest public final class MetricUtilitiesTest { + @Before + public void setUp() throws CertificateException { + final Context context = ApplicationProvider.getApplicationContext(); + } + @Test public void logApiCalledInitialPhase_nullInitPhaseMetricAndNegativeSequence_success() { MetricUtilities.logApiCalledInitialPhase(null, -1); @@ -49,4 +67,102 @@ public final class MetricUtilitiesTest { MetricUtilities.getHighlyUniqueInteger()); MetricUtilities.logApiCalledInitialPhase(validInitPhaseMetric, 1); } + + @Test + public void logApiCalledTotalCandidate_nullCandidateNegativeSequence_success() { + MetricUtilities.logApiCalledAggregateCandidate(null, -1); + } + + @Test + public void logApiCalledTotalCandidate_invalidCandidatePhasePositiveSequence_success() { + MetricUtilities.logApiCalledAggregateCandidate(new CandidateAggregateMetric(-1), 1); + } + + @Test + public void logApiCalledTotalCandidate_validPhaseMetric_success() { + MetricUtilities.logApiCalledAggregateCandidate( + new CandidateAggregateMetric(MetricUtilities.getHighlyUniqueInteger()), 1); + } + + @Test + public void logApiCalledNoUidFinal_nullNoUidFinalNegativeSequenceAndStatus_success() { + MetricUtilities.logApiCalledNoUidFinal(null, null, + -1, -1); + } + + @Test + public void logApiCalledNoUidFinal_invalidNoUidFinalPhasePositiveSequenceAndStatus_success() { + MetricUtilities.logApiCalledNoUidFinal(new ChosenProviderFinalPhaseMetric(-1, -1), + List.of(new CandidateBrowsingPhaseMetric()), 1, 1); + } + + @Test + public void logApiCalledNoUidFinal_validNoUidFinalMetric_success() { + MetricUtilities.logApiCalledNoUidFinal( + new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(), + MetricUtilities.getHighlyUniqueInteger()), + List.of(new CandidateBrowsingPhaseMetric()), 1, 1); + } + + @Test + public void logApiCalledCandidate_nullMapNullInitFinalNegativeSequence_success() { + MetricUtilities.logApiCalledCandidatePhase(null, -1, + null); + } + + @Test + public void logApiCalledCandidate_invalidProvidersCandidatePositiveSequence_success() { + Map<String, ProviderSession> testMap = new HashMap<>(); + testMap.put("s", null); + MetricUtilities.logApiCalledCandidatePhase(testMap, 1, + null); + } + + @Test + public void logApiCalledCandidateGet_nullMapFinalNegativeSequence_success() { + MetricUtilities.logApiCalledCandidateGetMetric(null, -1); + } + + @Test + public void logApiCalledCandidateGet_invalidProvidersCandidatePositiveSequence_success() { + Map<String, ProviderSession> testMap = new HashMap<>(); + testMap.put("s", null); + MetricUtilities.logApiCalledCandidateGetMetric(testMap, 1); + } + + @Test + public void logApiCalledAuthMetric_nullAuthMetricNegativeSequence_success() { + MetricUtilities.logApiCalledAuthenticationMetric(null, -1); + } + + @Test + public void logApiCalledAuthMetric_invalidAuthMetricPositiveSequence_success() { + MetricUtilities.logApiCalledAuthenticationMetric(new BrowsedAuthenticationMetric(-1), 1); + } + + @Test + public void logApiCalledAuthMetric_nullAuthMetricPositiveSequence_success() { + MetricUtilities.logApiCalledAuthenticationMetric( + new BrowsedAuthenticationMetric(MetricUtilities.getHighlyUniqueInteger()), -1); + } + + @Test + public void logApiCalledFinal_nullFinalNegativeSequenceAndStatus_success() { + MetricUtilities.logApiCalledFinalPhase(null, null, + -1, -1); + } + + @Test + public void logApiCalledFinal_invalidFinalPhasePositiveSequenceAndStatus_success() { + MetricUtilities.logApiCalledFinalPhase(new ChosenProviderFinalPhaseMetric(-1, -1), + List.of(new CandidateBrowsingPhaseMetric()), 1, 1); + } + + @Test + public void logApiCalledFinal_validFinalMetric_success() { + MetricUtilities.logApiCalledFinalPhase( + new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(), + MetricUtilities.getHighlyUniqueInteger()), + List.of(new CandidateBrowsingPhaseMetric()), 1, 1); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 4890f3e6cbf1..bcb0c6b5c269 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -250,9 +250,22 @@ public class ActivityStartInterceptorTest { } @Test - public void testInterceptQuietProfile() { - // GIVEN that the user the activity is starting as is currently in quiet mode + public void testInterceptQuietProfile_keepProfilesRunningEnabled() { + // GIVEN that the user the activity is starting as is currently in quiet mode and + // profiles are kept running when in quiet mode. when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); + when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true); + + // THEN calling intercept returns false because package also has to be suspended. + assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null)); + } + + @Test + public void testInterceptQuietProfile_keepProfilesRunningDisabled() { + // GIVEN that the user the activity is starting as is currently in quiet mode and + // profiles are stopped when in quiet mode (pre-U behavior, no profile app suspension). + when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); + when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false); // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null)); @@ -263,10 +276,28 @@ public class ActivityStartInterceptorTest { } @Test - public void testInterceptQuietProfileWhenPackageSuspended() { + public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningEnabled() { + // GIVEN that the user the activity is starting as is currently in quiet mode, + // the package is suspended and profiles are kept running while in quiet mode. + suspendPackage("com.test.suspending.package"); + when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); + when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true); + + // THEN calling intercept returns true + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null)); + + // THEN the returned intent is the quiet mode intent + assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID) + .filterEquals(mInterceptor.mIntent)); + } + + @Test + public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningDisabled() { + // GIVEN that the user the activity is starting as is currently in quiet mode, + // the package is suspended and profiles are stopped while in quiet mode. suspendPackage("com.test.suspending.package"); - // GIVEN that the user the activity is starting as is currently in quiet mode when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); + when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false); // THEN calling intercept returns true assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null)); diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp index 27640a5f51bc..84845c69fb27 100644 --- a/tests/InputMethodStressTest/Android.bp +++ b/tests/InputMethodStressTest/Android.bp @@ -30,7 +30,6 @@ android_test { ], test_suites: [ "general-tests", - "vts", ], data: [ ":SimpleTestIme", |