diff options
157 files changed, 4495 insertions, 2651 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index c76a43fa38f5..61cca3234f56 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -3088,7 +3088,9 @@ public class AlarmManagerService extends SystemService { + " does not belong to the calling uid " + callingUid); } synchronized (mLock) { - removeLocked(callingPackage, REMOVE_REASON_ALARM_CANCELLED); + removeAlarmsInternalLocked( + a -> (a.matches(callingPackage) && a.creatorUid == callingUid), + REMOVE_REASON_ALARM_CANCELLED); } } @@ -4285,10 +4287,25 @@ public class AlarmManagerService extends SystemService { } } - boolean lookForPackageLocked(String packageName) { - final ArrayList<Alarm> allAlarms = mAlarmStore.asList(); - for (final Alarm alarm : allAlarms) { - if (alarm.matches(packageName)) { + @GuardedBy("mLock") + boolean lookForPackageLocked(String packageName, int uid) { + // This is called extremely rarely, e.g. when the user opens the force-stop page in settings + // so the loops using an iterator should be fine. + for (final Alarm alarm : mAlarmStore.asList()) { + if (alarm.matches(packageName) && alarm.creatorUid == uid) { + return true; + } + } + final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.get(uid); + if (alarmsForUid != null) { + for (final Alarm alarm : alarmsForUid) { + if (alarm.matches(packageName)) { + return true; + } + } + } + for (final Alarm alarm : mPendingNonWakeupAlarms) { + if (alarm.matches(packageName) && alarm.creatorUid == uid) { return true; } } @@ -5269,7 +5286,7 @@ public class AlarmManagerService extends SystemService { case Intent.ACTION_QUERY_PACKAGE_RESTART: pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); for (String packageName : pkgList) { - if (lookForPackageLocked(packageName)) { + if (lookForPackageLocked(packageName, uid)) { setResultCode(Activity.RESULT_OK); return; } diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index d0ce70133414..12026aa3f72a 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -72,6 +72,20 @@ public abstract class Animator implements Cloneable { private static long sBackgroundPauseDelay = 1000; /** + * A cache of the values in a list. Used so that when calling the list, we have a copy + * of it in case the list is modified while iterating. The array can be reused to avoid + * allocation on every notification. + */ + private Object[] mCachedList; + + /** + * Tracks whether we've notified listeners of the onAnimationStart() event. This can be + * complex to keep track of since we notify listeners at different times depending on + * startDelay and whether start() was called before end(). + */ + boolean mStartListenersCalled = false; + + /** * Sets the duration for delaying pausing animators when apps go into the background. * Used by AnimationHandler when requested to pause animators. * @@ -158,16 +172,11 @@ public abstract class Animator implements Cloneable { * @see AnimatorPauseListener */ public void pause() { - if (isStarted() && !mPaused) { + // We only want to pause started Animators or animators that setCurrentPlayTime() + // have been called on. mStartListenerCalled will be true if seek has happened. + if ((isStarted() || mStartListenersCalled) && !mPaused) { mPaused = true; - if (mPauseListeners != null) { - ArrayList<AnimatorPauseListener> tmpListeners = - (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onAnimationPause(this); - } - } + notifyPauseListeners(AnimatorCaller.ON_PAUSE); } } @@ -184,14 +193,7 @@ public abstract class Animator implements Cloneable { public void resume() { if (mPaused) { mPaused = false; - if (mPauseListeners != null) { - ArrayList<AnimatorPauseListener> tmpListeners = - (ArrayList<AnimatorPauseListener>) mPauseListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onAnimationResume(this); - } - } + notifyPauseListeners(AnimatorCaller.ON_RESUME); } } @@ -450,6 +452,8 @@ public abstract class Animator implements Cloneable { if (mPauseListeners != null) { anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners); } + anim.mCachedList = null; + anim.mStartListenersCalled = false; return anim; } catch (CloneNotSupportedException e) { throw new AssertionError(); @@ -591,6 +595,86 @@ public abstract class Animator implements Cloneable { } /** + * Calls notification for each AnimatorListener. + * + * @param notification The notification method to call on each listener. + * @param isReverse When this is used with start/end, this is the isReverse parameter. For + * other calls, this is ignored. + */ + void notifyListeners( + AnimatorCaller<AnimatorListener, Animator> notification, + boolean isReverse + ) { + callOnList(mListeners, notification, this, isReverse); + } + + /** + * Call pause/resume on each AnimatorPauseListener. + * + * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each + * listener. + */ + void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) { + callOnList(mPauseListeners, notification, this, false); + } + + void notifyStartListeners(boolean isReversing) { + boolean startListenersCalled = mStartListenersCalled; + mStartListenersCalled = true; + if (mListeners != null && !startListenersCalled) { + notifyListeners(AnimatorCaller.ON_START, isReversing); + } + } + + void notifyEndListeners(boolean isReversing) { + boolean startListenersCalled = mStartListenersCalled; + mStartListenersCalled = false; + if (mListeners != null && startListenersCalled) { + notifyListeners(AnimatorCaller.ON_END, isReversing); + } + } + + /** + * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and + * <code>isReverse</code> as parameters. + * + * @param list The list of items to make calls on. + * @param call The method to call for each item in list. + * @param animator The animator parameter of call. + * @param isReverse The isReverse parameter of call. + * @param <T> The item type of list + * @param <A> The Animator type of animator. + */ + <T, A> void callOnList( + ArrayList<T> list, + AnimatorCaller<T, A> call, + A animator, + boolean isReverse + ) { + int size = list == null ? 0 : list.size(); + if (size > 0) { + // Try to reuse mCacheList to store the items of list. + Object[] array; + if (mCachedList == null || mCachedList.length < size) { + array = new Object[size]; + } else { + array = mCachedList; + // Clear it in case there is some reentrancy + mCachedList = null; + } + list.toArray(array); + for (int i = 0; i < size; i++) { + //noinspection unchecked + T item = (T) array[i]; + call.call(item, animator, isReverse); + array[i] = null; + } + // Store it for the next call so we can reuse this array, if needed. + mCachedList = array; + } + } + + /** * <p>An animation listener receives notifications from an animation. * Notifications indicate animation related events, such as the end or the * repetition of the animation.</p> @@ -748,4 +832,29 @@ public abstract class Animator implements Cloneable { return clone; } } + + /** + * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to + * make a call on all children of a list. This can be for start, stop, pause, cancel, update, + * etc notifications. + * + * @param <T> The type of listener to make the call on + * @param <A> The type of animator that is passed as a parameter + */ + interface AnimatorCaller<T, A> { + void call(T listener, A animator, boolean isReverse); + + AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart; + AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd; + AnimatorCaller<AnimatorListener, Animator> ON_CANCEL = + (listener, animator, isReverse) -> listener.onAnimationCancel(animator); + AnimatorCaller<AnimatorListener, Animator> ON_REPEAT = + (listener, animator, isReverse) -> listener.onAnimationRepeat(animator); + AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE = + (listener, animator, isReverse) -> listener.onAnimationPause(animator); + AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME = + (listener, animator, isReverse) -> listener.onAnimationResume(animator); + AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE = + (listener, animator, isReverse) -> listener.onAnimationUpdate(animator); + } } diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 257adfe390d6..60659dc12342 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.function.Consumer; /** * This class plays a set of {@link Animator} objects in the specified order. Animations @@ -188,11 +189,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim */ private long[] mChildStartAndStopTimes; - /** - * Tracks whether we've notified listeners of the onAnimationStart() event. - */ - private boolean mStartListenersCalled; - // This is to work around a bug in b/34736819. This needs to be removed once app team // fixes their side. private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() { @@ -423,25 +419,29 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } - if (isStarted()) { - ArrayList<AnimatorListener> tmpListeners = null; - if (mListeners != null) { - tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); - int size = tmpListeners.size(); - for (int i = 0; i < size; i++) { - tmpListeners.get(i).onAnimationCancel(this); - } - } - ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet); - int setSize = playingSet.size(); - for (int i = 0; i < setSize; i++) { - playingSet.get(i).mAnimation.cancel(); - } + if (isStarted() || mStartListenersCalled) { + notifyListeners(AnimatorCaller.ON_CANCEL, false); + callOnPlayingSet(Animator::cancel); mPlayingSet.clear(); endAnimation(); } } + /** + * Calls consumer on every Animator of mPlayingSet. + * + * @param consumer The method to call on every Animator of mPlayingSet. + */ + private void callOnPlayingSet(Consumer<Animator> consumer) { + final ArrayList<Node> list = mPlayingSet; + final int size = list.size(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < size; i++) { + final Animator animator = list.get(i).mAnimation; + consumer.accept(animator); + } + } + // Force all the animations to end when the duration scale is 0. private void forceToEnd() { if (mEndCanBeCalled) { @@ -481,13 +481,13 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim return; } if (isStarted()) { + mStarted = false; // don't allow reentrancy // Iterate the animations that haven't finished or haven't started, and end them. if (mReversing) { // Between start() and first frame, mLastEventId would be unset (i.e. -1) mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId; - while (mLastEventId > 0) { - mLastEventId = mLastEventId - 1; - AnimationEvent event = mEvents.get(mLastEventId); + for (int eventId = mLastEventId - 1; eventId >= 0; eventId--) { + AnimationEvent event = mEvents.get(eventId); Animator anim = event.mNode.mAnimation; if (mNodeMap.get(anim).mEnded) { continue; @@ -503,11 +503,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } } else { - while (mLastEventId < mEvents.size() - 1) { + for (int eventId = mLastEventId + 1; eventId < mEvents.size(); eventId++) { // Avoid potential reentrant loop caused by child animators manipulating // AnimatorSet's lifecycle (i.e. not a recommended approach). - mLastEventId = mLastEventId + 1; - AnimationEvent event = mEvents.get(mLastEventId); + AnimationEvent event = mEvents.get(eventId); Animator anim = event.mNode.mAnimation; if (mNodeMap.get(anim).mEnded) { continue; @@ -522,7 +521,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } } - mPlayingSet.clear(); } endAnimation(); } @@ -662,6 +660,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim super.pause(); if (!previouslyPaused && mPaused) { mPauseTime = -1; + callOnPlayingSet(Animator::pause); } } @@ -676,6 +675,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (mPauseTime >= 0) { addAnimationCallback(0); } + callOnPlayingSet(Animator::resume); } } @@ -716,6 +716,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } + if (inReverse == mReversing && selfPulse == mSelfPulse && mStarted) { + // It is already started + return; + } mStarted = true; mSelfPulse = selfPulse; mPaused = false; @@ -749,32 +753,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } - private void notifyStartListeners(boolean inReverse) { - if (mListeners != null && !mStartListenersCalled) { - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - AnimatorListener listener = tmpListeners.get(i); - listener.onAnimationStart(this, inReverse); - } - } - mStartListenersCalled = true; - } - - private void notifyEndListeners(boolean inReverse) { - if (mListeners != null && mStartListenersCalled) { - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - AnimatorListener listener = tmpListeners.get(i); - listener.onAnimationEnd(this, inReverse); - } - } - mStartListenersCalled = false; - } - // Returns true if set is empty or contains nothing but animator sets with no start delay. private static boolean isEmptySet(AnimatorSet set) { if (set.getStartDelay() > 0) { @@ -941,12 +919,18 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim lastPlayTime - node.mStartTime, notify ); + if (notify) { + mPlayingSet.remove(node); + } } else if (start <= currentPlayTime && currentPlayTime <= end) { animator.animateSkipToEnds( currentPlayTime - node.mStartTime, lastPlayTime - node.mStartTime, notify ); + if (notify && !mPlayingSet.contains(node)) { + mPlayingSet.add(node); + } } } } @@ -974,12 +958,18 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim lastPlayTime - node.mStartTime, notify ); + if (notify) { + mPlayingSet.remove(node); + } } else if (start <= currentPlayTime && currentPlayTime <= end) { animator.animateSkipToEnds( currentPlayTime - node.mStartTime, lastPlayTime - node.mStartTime, notify ); + if (notify && !mPlayingSet.contains(node)) { + mPlayingSet.add(node); + } } } } @@ -1120,8 +1110,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim mSeekState.setPlayTime(0, mReversing); } } - animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true); mSeekState.setPlayTime(playTime, mReversing); + animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true); } /** diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 7009725aa32b..5d69f8b80799 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -199,13 +199,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio private boolean mStarted = false; /** - * Tracks whether we've notified listeners of the onAnimationStart() event. This can be - * complex to keep track of since we notify listeners at different times depending on - * startDelay and whether start() was called before end(). - */ - private boolean mStartListenersCalled = false; - - /** * Flag that denotes whether the animation is set up and ready to go. Used to * set up animation that has not yet been started. */ @@ -1108,30 +1101,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio } } - private void notifyStartListeners(boolean isReversing) { - if (mListeners != null && !mStartListenersCalled) { - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onAnimationStart(this, isReversing); - } - } - mStartListenersCalled = true; - } - - private void notifyEndListeners(boolean isReversing) { - if (mListeners != null && mStartListenersCalled) { - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onAnimationEnd(this, isReversing); - } - } - mStartListenersCalled = false; - } - /** * Start the animation playing. This version of start() takes a boolean flag that indicates * whether the animation should play in reverse. The flag is usually false, but may be set @@ -1149,6 +1118,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } + if (playBackwards == mResumed && mSelfPulse == !mSuppressSelfPulseRequested && mStarted) { + // already started + return; + } mReversing = playBackwards; mSelfPulse = !mSuppressSelfPulseRequested; // Special case: reversing from seek-to-0 should act as if not seeked at all. @@ -1219,23 +1192,14 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // Only cancel if the animation is actually running or has been started and is about // to run // Only notify listeners if the animator has actually started - if ((mStarted || mRunning) && mListeners != null) { + if ((mStarted || mRunning || mStartListenersCalled) && mListeners != null) { if (!mRunning) { // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(mReversing); } - int listenersSize = mListeners.size(); - if (listenersSize > 0) { - ArrayList<AnimatorListener> tmpListeners = - (ArrayList<AnimatorListener>) mListeners.clone(); - for (int i = 0; i < listenersSize; i++) { - AnimatorListener listener = tmpListeners.get(i); - listener.onAnimationCancel(this); - } - } + notifyListeners(AnimatorCaller.ON_CANCEL, false); } endAnimation(); - } @Override @@ -1338,11 +1302,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If it's not yet running, then start listeners weren't called. Call them now. notifyStartListeners(mReversing); } - mRunning = false; - mStarted = false; mLastFrameTime = -1; mFirstFrameTime = -1; mStartTime = -1; + mRunning = false; + mStarted = false; notifyEndListeners(mReversing); // mReversing needs to be reset *after* notifying the listeners for the end callbacks. mReversing = false; @@ -1435,12 +1399,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio done = true; } else if (newIteration && !lastIterationFinished) { // Time to repeat - if (mListeners != null) { - int numListeners = mListeners.size(); - for (int i = 0; i < numListeners; ++i) { - mListeners.get(i).onAnimationRepeat(this); - } - } + notifyListeners(AnimatorCaller.ON_REPEAT, false); } else if (lastIterationFinished) { done = true; } @@ -1493,13 +1452,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio iteration = Math.min(iteration, mRepeatCount); lastIteration = Math.min(lastIteration, mRepeatCount); - if (iteration != lastIteration) { - if (mListeners != null) { - int numListeners = mListeners.size(); - for (int i = 0; i < numListeners; ++i) { - mListeners.get(i).onAnimationRepeat(this); - } - } + if (notify && iteration != lastIteration) { + notifyListeners(AnimatorCaller.ON_REPEAT, false); } } @@ -1697,11 +1651,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction); } - if (mUpdateListeners != null) { - int numListeners = mUpdateListeners.size(); - for (int i = 0; i < numListeners; ++i) { - mUpdateListeners.get(i).onAnimationUpdate(this); - } + if (mSeekFraction >= 0 || mStartListenersCalled) { + callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false); } } @@ -1718,7 +1669,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio anim.mRunning = false; anim.mPaused = false; anim.mResumed = false; - anim.mStartListenersCalled = false; anim.mStartTime = -1; anim.mStartTimeCommitted = false; anim.mAnimationEndRequested = false; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 981f14020370..929c07bc1dc5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -234,7 +234,7 @@ public class ActivityManager { * Map of callbacks that have registered for {@link UidFrozenStateChanged} events. * Will be called when a Uid has become frozen or unfrozen. */ - final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks = + private final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks = new ArrayMap<>(); private final IUidFrozenStateChangedCallback mFrozenStateChangedCallback = @@ -284,6 +284,8 @@ public class ActivityManager { public @interface UidFrozenState {} /** + * Notify the client that the frozen states of an array of UIDs have changed. + * * @param uids The UIDs for which the frozen state has changed * @param frozenStates Frozen state for each UID index, Will be set to * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN} @@ -316,9 +318,11 @@ public class ActivityManager { public void registerUidFrozenStateChangedCallback( @NonNull Executor executor, @NonNull UidFrozenStateChangedCallback callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); synchronized (mFrozenStateChangedCallbacks) { if (mFrozenStateChangedCallbacks.containsKey(callback)) { - throw new IllegalArgumentException("Callback already registered: " + callback); + throw new IllegalStateException("Callback already registered: " + callback); } mFrozenStateChangedCallbacks.put(callback, executor); if (mFrozenStateChangedCallbacks.size() > 1) { @@ -344,6 +348,7 @@ public class ActivityManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void unregisterUidFrozenStateChangedCallback( @NonNull UidFrozenStateChangedCallback callback) { + Preconditions.checkNotNull(callback, "callback cannot be null"); synchronized (mFrozenStateChangedCallbacks) { mFrozenStateChangedCallbacks.remove(callback); if (mFrozenStateChangedCallbacks.isEmpty()) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 2879062248a8..403acad6bba1 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -879,6 +879,8 @@ interface IActivityManager { /** Logs API state change to associate with an FGS, used for FGS Type Metrics */ void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b9c671a8f3ea..21e2a131bcac 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -10134,25 +10134,9 @@ public abstract class PackageManager { /** * Register an application dex module with the package manager. - * The package manager will keep track of the given module for future optimizations. * - * Dex module optimizations will disable the classpath checking at runtime. The client bares - * the responsibility to ensure that the static assumptions on classes in the optimized code - * hold at runtime (e.g. there's no duplicate classes in the classpath). - * - * Note that the package manager already keeps track of dex modules loaded with - * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}. - * This can be called for an eager registration. - * - * The call might take a while and the results will be posted on the main thread, using - * the given callback. - * - * If the module is intended to be shared with other apps, make sure that the file - * permissions allow for it. - * If at registration time the permissions allow for others to read it, the module would - * be marked as a shared module which might undergo a different optimization strategy. - * (usually shared modules will generated larger optimizations artifacts, - * taking more disk space). + * This call no longer does anything. If a callback is given it is called with a false success + * value. * * @param dexModulePath the absolute path of the dex module. * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java index 221e89a6a76f..310ceb3aeb91 100644 --- a/core/java/android/os/PermissionEnforcer.java +++ b/core/java/android/os/PermissionEnforcer.java @@ -18,9 +18,11 @@ package android.os; import android.annotation.NonNull; import android.annotation.SystemService; +import android.app.AppOpsManager; import android.content.AttributionSource; import android.content.Context; import android.content.PermissionChecker; +import android.content.pm.PackageManager; import android.permission.PermissionCheckerManager; /** @@ -40,6 +42,7 @@ import android.permission.PermissionCheckerManager; public class PermissionEnforcer { private final Context mContext; + private static final String ACCESS_DENIED = "Access denied, requires: "; /** Protected constructor. Allows subclasses to instantiate an object * without using a Context. @@ -59,11 +62,42 @@ public class PermissionEnforcer { mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */); } + @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck") + @PermissionCheckerManager.PermissionResult + protected int checkPermission(@NonNull String permission, int pid, int uid) { + if (mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) { + return PermissionCheckerManager.PERMISSION_GRANTED; + } + return PermissionCheckerManager.PERMISSION_HARD_DENIED; + } + + private boolean anyAppOps(@NonNull String[] permissions) { + for (String permission : permissions) { + if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) { + return true; + } + } + return false; + } + public void enforcePermission(@NonNull String permission, @NonNull AttributionSource source) throws SecurityException { int result = checkPermission(permission, source); if (result != PermissionCheckerManager.PERMISSION_GRANTED) { - throw new SecurityException("Access denied, requires: " + permission); + throw new SecurityException(ACCESS_DENIED + permission); + } + } + + public void enforcePermission(@NonNull String permission, int pid, int uid) + throws SecurityException { + if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) { + AttributionSource source = new AttributionSource(uid, null, null); + enforcePermission(permission, source); + return; + } + int result = checkPermission(permission, pid, uid); + if (result != PermissionCheckerManager.PERMISSION_GRANTED) { + throw new SecurityException(ACCESS_DENIED + permission); } } @@ -72,7 +106,23 @@ public class PermissionEnforcer { for (String permission : permissions) { int result = checkPermission(permission, source); if (result != PermissionCheckerManager.PERMISSION_GRANTED) { - throw new SecurityException("Access denied, requires: allOf={" + throw new SecurityException(ACCESS_DENIED + "allOf={" + + String.join(", ", permissions) + "}"); + } + } + } + + public void enforcePermissionAllOf(@NonNull String[] permissions, + int pid, int uid) throws SecurityException { + if (anyAppOps(permissions)) { + AttributionSource source = new AttributionSource(uid, null, null); + enforcePermissionAllOf(permissions, source); + return; + } + for (String permission : permissions) { + int result = checkPermission(permission, pid, uid); + if (result != PermissionCheckerManager.PERMISSION_GRANTED) { + throw new SecurityException(ACCESS_DENIED + "allOf={" + String.join(", ", permissions) + "}"); } } @@ -86,7 +136,24 @@ public class PermissionEnforcer { return; } } - throw new SecurityException("Access denied, requires: anyOf={" + throw new SecurityException(ACCESS_DENIED + "anyOf={" + + String.join(", ", permissions) + "}"); + } + + public void enforcePermissionAnyOf(@NonNull String[] permissions, + int pid, int uid) throws SecurityException { + if (anyAppOps(permissions)) { + AttributionSource source = new AttributionSource(uid, null, null); + enforcePermissionAnyOf(permissions, source); + return; + } + for (String permission : permissions) { + int result = checkPermission(permission, pid, uid); + if (result == PermissionCheckerManager.PERMISSION_GRANTED) { + return; + } + } + throw new SecurityException(ACCESS_DENIED + "anyOf={" + String.join(", ", permissions) + "}"); } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index b117a9afb6f5..6f2a915cee46 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -141,12 +141,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private int mRingerMode; private int mZenMode; private boolean mPlaySample; + private final boolean mDeviceHasProductStrategies; private static final int MSG_SET_STREAM_VOLUME = 0; private static final int MSG_START_SAMPLE = 1; private static final int MSG_STOP_SAMPLE = 2; private static final int MSG_INIT_SAMPLE = 3; + private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500; private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500); private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500); private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000); @@ -170,6 +173,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba boolean playSample) { mContext = context; mAudioManager = context.getSystemService(AudioManager.class); + mDeviceHasProductStrategies = hasAudioProductStrategies(); mNotificationManager = context.getSystemService(NotificationManager.class); mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy(); mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy @@ -186,7 +190,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } mZenMode = mNotificationManager.getZenMode(); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType); mAttributes = getAudioAttributesForLegacyStreamType( mStreamType); @@ -213,6 +217,12 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mDefaultUri = defaultUri; } + /** + * DO NOT CALL every time this is needed, use once in constructor, + * read mDeviceHasProductStrategies instead + * @return true if stream types are used for volume management, false if volume groups are + * used for volume management + */ private boolean hasAudioProductStrategies() { return AudioManager.getAudioProductStrategies().size() > 0; } @@ -330,6 +340,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba onInitSample(); } break; + case MSG_UPDATE_SLIDER_MAYBE_LATER: + onUpdateSliderMaybeLater(); + break; default: Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); } @@ -353,6 +366,21 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba : isDelay() ? START_SAMPLE_DELAY_MS : 0); } + private void onUpdateSliderMaybeLater() { + if (isDelay()) { + postUpdateSliderMaybeLater(); + return; + } + updateSlider(); + } + + private void postUpdateSliderMaybeLater() { + if (mHandler == null) return; + mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER), + CHECK_UPDATE_SLIDER_LATER_MS); + } + // After stop volume it needs to add a small delay when playing volume or set stream. // It is because the call volume is from the earpiece and the alarm/ring/media // is from the speaker. If play the alarm volume or set alarm stream right after stop @@ -422,7 +450,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mReceiver.setListening(false); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { unregisterVolumeGroupCb(); } mSeekBar.setOnSeekBarChangeListener(null); @@ -442,7 +470,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]), false, mVolumeObserver); mReceiver.setListening(true); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { registerVolumeGroupCb(); } } @@ -466,6 +494,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mLastProgress = progress; mHandler.removeMessages(MSG_SET_STREAM_VOLUME); mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME), isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0); } @@ -609,7 +638,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); - if (hasAudioProductStrategies() && !isDelay()) { + if (mDeviceHasProductStrategies && !isDelay()) { updateVolumeSlider(streamType, streamValue); } } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { @@ -621,9 +650,16 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); - if (hasAudioProductStrategies() && !isDelay()) { - int streamVolume = mAudioManager.getStreamVolume(streamType); - updateVolumeSlider(streamType, streamVolume); + + if (mDeviceHasProductStrategies) { + if (isDelay()) { + // not the right time to update the sliders, try again later + postUpdateSliderMaybeLater(); + } else { + int streamVolume = mAudioManager.getStreamVolume(streamType); + updateVolumeSlider(streamType, streamVolume); + } + } else { int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType); if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 4c6bd671df7b..6201b3a91eff 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -234,8 +234,8 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true"); - DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); - DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); + DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false"); + DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 4895aed60a3a..0db52aaa8b3d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3896,10 +3896,12 @@ public final class SurfaceControl implements Parcelable { float currentBufferRatio, float desiredRatio) { checkPreconditions(sc); if (!Float.isFinite(currentBufferRatio) || currentBufferRatio < 1.0f) { - throw new IllegalArgumentException("currentBufferRatio must be finite && >= 1.0f"); + throw new IllegalArgumentException( + "currentBufferRatio must be finite && >= 1.0f; got " + currentBufferRatio); } if (!Float.isFinite(desiredRatio) || desiredRatio < 1.0f) { - throw new IllegalArgumentException("desiredRatio must be finite && >= 1.0f"); + throw new IllegalArgumentException( + "desiredRatio must be finite && >= 1.0f; got " + desiredRatio); } nativeSetExtendedRangeBrightness(mNativeObject, sc.mNativeObject, currentBufferRatio, desiredRatio); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index d8b6b7b25a24..b46a68c1d5fd 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -851,14 +851,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); - // Only control visibility if we're not hardware-accelerated. Otherwise we'll - // let renderthread drive since offscreen SurfaceControls should not be visible. - if (!isHardwareAccelerated()) { - if (mViewVisibility) { - surfaceUpdateTransaction.show(mSurfaceControl); - } else { - surfaceUpdateTransaction.hide(mSurfaceControl); - } + if (mViewVisibility) { + surfaceUpdateTransaction.show(mSurfaceControl); + } else { + surfaceUpdateTransaction.hide(mSurfaceControl); } updateBackgroundVisibility(surfaceUpdateTransaction); @@ -1421,10 +1417,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private final Rect mRTLastReportedPosition = new Rect(); + private final Point mRTLastReportedSurfaceSize = new Point(); private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener { private final int mRtSurfaceWidth; private final int mRtSurfaceHeight; + private boolean mRtFirst = true; private final SurfaceControl.Transaction mPositionChangedTransaction = new SurfaceControl.Transaction(); @@ -1435,6 +1433,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { + if (!mRtFirst && (mRTLastReportedPosition.left == left + && mRTLastReportedPosition.top == top + && mRTLastReportedPosition.right == right + && mRTLastReportedPosition.bottom == bottom + && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth + && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight)) { + return; + } + mRtFirst = false; try { if (DEBUG_POSITION) { Log.d(TAG, String.format( @@ -1445,8 +1452,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } synchronized (mSurfaceControlLock) { if (mSurfaceControl == null) return; - mRTLastReportedPosition.set(left, top, right, bottom); + mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight); onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl, mRTLastReportedPosition.left /*positionLeft*/, mRTLastReportedPosition.top /*positionTop*/, @@ -1454,8 +1461,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall / (float) mRtSurfaceWidth /*postScaleX*/, mRTLastReportedPosition.height() / (float) mRtSurfaceHeight /*postScaleY*/); - - mPositionChangedTransaction.show(mSurfaceControl); + if (mViewVisibility) { + // b/131239825 + mPositionChangedTransaction.show(mSurfaceControl); + } } applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); } catch (Exception ex) { @@ -1481,6 +1490,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall System.identityHashCode(this), frameNumber)); } mRTLastReportedPosition.setEmpty(); + mRTLastReportedSurfaceSize.set(-1, -1); // positionLost can be called while UI thread is un-paused. synchronized (mSurfaceControlLock) { diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING index ecb98f9ce801..1e39716988a9 100644 --- a/core/java/android/view/TEST_MAPPING +++ b/core/java/android/view/TEST_MAPPING @@ -42,6 +42,9 @@ ], "imports": [ { + "path": "cts/tests/surfacecontrol" + }, + { "path": "cts/tests/tests/uirendering" } ] diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 9d82b7900689..b17d2b947c66 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -2648,7 +2648,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Gets if the node's accessibility data is considered sensitive. * - * @return True if the node is editable, false otherwise. + * @return True if the node's data is considered sensitive, false otherwise. * @see View#isAccessibilityDataSensitive() */ public boolean isAccessibilityDataSensitive() { diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java index e7207fa0dfa0..c4d43bcfdb24 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.graphics.RectF; import android.inputmethodservice.InputMethodService; +import android.os.CancellationSignal; import android.os.Parcel; import android.os.Parcelable; import android.view.MotionEvent; @@ -33,18 +34,20 @@ import java.util.concurrent.Executor; import java.util.function.IntConsumer; /** - * Base class for Stylus handwriting gesture. - * + * Base class for stylus handwriting gestures. + * <p> * During a stylus handwriting session, user can perform a stylus gesture operation like * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an - * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using + * area of text. IME is responsible for listening to stylus {@link MotionEvent}s using * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a * gesture operation. - * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture}, - * , {@code Granularity} helps pick the correct granular level of text like word level + * <p> + * While creating gesture operations {@link SelectGesture} and {@link DeleteGesture}, + * {@code Granularity} helps pick the correct granular level of text like word level * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}. * * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer) + * @see InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal) * @see InputMethodService#onStartStylusHandwriting() */ public abstract class HandwritingGesture { diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java index 5353bc6983ee..715fc9abe4ba 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -41,6 +41,9 @@ public class HttpAuthHandler extends Handler { * are suitable for use. Credentials are not suitable if they have * previously been rejected by the server for the current request. * + * <p class="note"><b>Note:</b> The host application must call this method + * on the host application's UI Thread. + * * @return whether the credentials are suitable for use * @see WebView#getHttpAuthUsernamePassword */ @@ -50,6 +53,9 @@ public class HttpAuthHandler extends Handler { /** * Instructs the WebView to cancel the authentication request. + * + * <p class="note"><b>Note:</b> The host application must call this method + * on the host application's UI Thread. */ public void cancel() { } @@ -58,6 +64,9 @@ public class HttpAuthHandler extends Handler { * Instructs the WebView to proceed with the authentication with the given * credentials. Credentials for use with this method can be retrieved from * the WebView's store using {@link WebView#getHttpAuthUsernamePassword}. + * + * <p class="note"><b>Note:</b> The host application must call this method + * on the host application's UI Thread. */ public void proceed(String username, String password) { } diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 7b6e1a370479..55f09f110f88 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -455,6 +455,9 @@ public class WebViewClient { * {@link HttpAuthHandler} to set the WebView's response to the request. * The default behavior is to cancel the request. * + * <p class="note"><b>Note:</b> The supplied HttpAuthHandler must be used on + * the UI thread. + * * @param view the WebView that is initiating the callback * @param handler the HttpAuthHandler used to set the WebView's response * @param host the host requiring authentication diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java index 2f3b662af6a7..65fbb03bf967 100644 --- a/core/java/com/android/internal/expresslog/Histogram.java +++ b/core/java/com/android/internal/expresslog/Histogram.java @@ -16,10 +16,14 @@ package com.android.internal.expresslog; +import android.annotation.FloatRange; +import android.annotation.IntRange; import android.annotation.NonNull; import com.android.internal.util.FrameworkStatsLog; +import java.util.Arrays; + /** Histogram encapsulates StatsD write API calls */ public final class Histogram { @@ -28,7 +32,8 @@ public final class Histogram { /** * Creates Histogram metric logging wrapper - * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog + * + * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog * @param binOptions to calculate bin index for samples * @hide */ @@ -39,6 +44,7 @@ public final class Histogram { /** * Logs increment sample count for automatically calculated bin + * * @param sample value * @hide */ @@ -52,6 +58,7 @@ public final class Histogram { public interface BinOptions { /** * Returns bins count to be used by a histogram + * * @return bins count used to initialize Options, including overflow & underflow bins * @hide */ @@ -61,6 +68,7 @@ public final class Histogram { * Returns bin index for the input sample value * index == 0 stands for underflow * index == getBinsCount() - 1 stands for overflow + * * @return zero based index * @hide */ @@ -76,17 +84,19 @@ public final class Histogram { private final float mBinSize; /** - * Creates otpions for uniform (linear) sized bins - * @param binCount amount of histogram bins. 2 bin indexes will be calculated - * automatically to represent undeflow & overflow bins - * @param minValue is included in the first bin, values less than minValue - * go to underflow bin + * Creates options for uniform (linear) sized bins + * + * @param binCount amount of histogram bins. 2 bin indexes will be calculated + * automatically to represent underflow & overflow bins + * @param minValue is included in the first bin, values less than minValue + * go to underflow bin * @param exclusiveMaxValue is included in the overflow bucket. For accurate - measure up to kMax, then exclusiveMaxValue + * measure up to kMax, then exclusiveMaxValue * should be set to kMax + 1 * @hide */ - public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) { + public UniformOptions(@IntRange(from = 1) int binCount, float minValue, + float exclusiveMaxValue) { if (binCount < 1) { throw new IllegalArgumentException("Bin count should be positive number"); } @@ -99,7 +109,7 @@ public final class Histogram { mExclusiveMaxValue = exclusiveMaxValue; mBinSize = (mExclusiveMaxValue - minValue) / binCount; - // Implicitly add 2 for the extra undeflow & overflow bins + // Implicitly add 2 for the extra underflow & overflow bins mBinCount = binCount + 2; } @@ -120,4 +130,92 @@ public final class Histogram { return (int) ((sample - mMinValue) / mBinSize + 1); } } + + /** Used by Histogram to map data sample to corresponding bin for scaled bins */ + public static final class ScaledRangeOptions implements BinOptions { + // store minimum value per bin + final long[] mBins; + + /** + * Creates options for scaled range bins + * + * @param binCount amount of histogram bins. 2 bin indexes will be calculated + * automatically to represent underflow & overflow bins + * @param minValue is included in the first bin, values less than minValue + * go to underflow bin + * @param firstBinWidth used to represent first bin width and as a reference to calculate + * width for consecutive bins + * @param scaleFactor used to calculate width for consecutive bins + * @hide + */ + public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue, + @FloatRange(from = 1.f) float firstBinWidth, + @FloatRange(from = 1.f) float scaleFactor) { + if (binCount < 1) { + throw new IllegalArgumentException("Bin count should be positive number"); + } + + if (firstBinWidth < 1.f) { + throw new IllegalArgumentException( + "First bin width invalid (should be 1.f at minimum)"); + } + + if (scaleFactor < 1.f) { + throw new IllegalArgumentException( + "Scaled factor invalid (should be 1.f at minimum)"); + } + + // precalculating bins ranges (no need to create a bin for underflow reference value) + mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor); + } + + @Override + public int getBinsCount() { + return mBins.length + 1; + } + + @Override + public int getBinForSample(float sample) { + if (sample < mBins[0]) { + // goes to underflow + return 0; + } else if (sample >= mBins[mBins.length - 1]) { + // goes to overflow + return mBins.length; + } + + return lower_bound(mBins, (long) sample) + 1; + } + + // To find lower bound using binary search implementation of Arrays utility class + private static int lower_bound(long[] array, long sample) { + int index = Arrays.binarySearch(array, sample); + // If key is not present in the array + if (index < 0) { + // Index specify the position of the key when inserted in the sorted array + // so the element currently present at this position will be the lower bound + return Math.abs(index) - 2; + } + return index; + } + + private static long[] initBins(int count, int minValue, float firstBinWidth, + float scaleFactor) { + long[] bins = new long[count]; + bins[0] = minValue; + double lastWidth = firstBinWidth; + for (int i = 1; i < count; i++) { + // current bin minValue = previous bin width * scaleFactor + double currentBinMinValue = bins[i - 1] + lastWidth; + if (currentBinMinValue > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "Attempted to create a bucket larger than maxint"); + } + + bins[i] = (long) currentBinMinValue; + lastWidth *= scaleFactor; + } + return bins; + } + } } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 5cab674eab05..1172f7ba447a 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -1297,6 +1297,17 @@ public class TransitionAnimation { return set; } + /** Sets the default attributes of the screenshot layer used for animation. */ + public static void configureScreenshotLayer(SurfaceControl.Transaction t, SurfaceControl layer, + ScreenCapture.ScreenshotHardwareBuffer buffer) { + t.setBuffer(layer, buffer.getHardwareBuffer()); + t.setDataSpace(layer, buffer.getColorSpace().getDataSpace()); + // Avoid showing dimming effect for HDR content when running animation. + if (buffer.containsHdrLayers()) { + t.setDimmingEnabled(layer, false); + } + } + /** Returns whether the hardware buffer passed in is marked as protected. */ public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) { return (hardwareBuffer.getUsage() & HardwareBuffer.USAGE_PROTECTED_CONTENT) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index d62f1cfcbddf..ce806a0fcc08 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -2296,7 +2296,9 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, // region shared with the child process we reduce the number of pages that // transition to the private-dirty state when malloc adjusts the meta-data // on each of the pages it is managing after the fork. - mallopt(M_PURGE, 0); + if (mallopt(M_PURGE_ALL, 0) != 1) { + mallopt(M_PURGE, 0); + } } pid_t pid = fork(); diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 16a8bb7280a4..710a70a32955 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -116,7 +116,8 @@ <com.android.internal.widget.NotificationVanishingFrameLayout android:layout_width="match_parent" - android:layout_height="@dimen/notification_headerless_line_height" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_headerless_line_height" > <!-- This is the simplest way to keep this text vertically centered without gravity="center_vertical" which causes jumpiness in expansion animations. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 80bf7955c030..5bb86dc4b404 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -356,7 +356,7 @@ <dimen name="notification_headerless_margin_twoline">20dp</dimen> <!-- The height of each of the 1 or 2 lines in the headerless notification template --> - <dimen name="notification_headerless_line_height">24sp</dimen> + <dimen name="notification_headerless_line_height">24dp</dimen> <!-- vertical margin for the headerless notification content --> <dimen name="notification_headerless_min_height">56dp</dimen> diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java index 7a1de0c5d4fe..a7538701807a 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java @@ -435,9 +435,11 @@ public class AnimatorSetActivityTest { mActivityRule.runOnUiThread(s::start); while (!listener.endIsCalled) { - boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted() || - a4.isStarted() || a5.isStarted(); - assertEquals(passedStartDelay, s.isRunning()); + mActivityRule.runOnUiThread(() -> { + boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted() + || a4.isStarted() || a5.isStarted(); + assertEquals(passedStartDelay, s.isRunning()); + }); Thread.sleep(50); } assertFalse(s.isRunning()); diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java new file mode 100644 index 000000000000..43266a51502b --- /dev/null +++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.animation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.util.PollingCheck; +import android.view.View; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.filters.MediumTest; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@MediumTest +public class AnimatorSetCallsTest { + @Rule + public final ActivityScenarioRule<AnimatorSetActivity> mRule = + new ActivityScenarioRule<>(AnimatorSetActivity.class); + + private AnimatorSetActivity mActivity; + private AnimatorSet mSet1; + private AnimatorSet mSet2; + private ObjectAnimator mAnimator; + private CountListener mListener1; + private CountListener mListener2; + private CountListener mListener3; + + @Before + public void setUp() throws Exception { + mRule.getScenario().onActivity((activity) -> { + mActivity = activity; + View square = mActivity.findViewById(R.id.square1); + + mSet1 = new AnimatorSet(); + mListener1 = new CountListener(); + mSet1.addListener(mListener1); + mSet1.addPauseListener(mListener1); + + mSet2 = new AnimatorSet(); + mListener2 = new CountListener(); + mSet2.addListener(mListener2); + mSet2.addPauseListener(mListener2); + + mAnimator = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f); + mListener3 = new CountListener(); + mAnimator.addListener(mListener3); + mAnimator.addPauseListener(mListener3); + mAnimator.setDuration(1); + + mSet2.play(mAnimator); + mSet1.play(mSet2); + }); + } + + @Test + public void startEndCalledOnChildren() { + mRule.getScenario().onActivity((a) -> mSet1.start()); + waitForOnUiThread(() -> mListener1.endForward > 0); + + // only startForward and endForward should have been called once + mListener1.assertValues( + 1, 0, 1, 0, 0, 0, 0, 0 + ); + mListener2.assertValues( + 1, 0, 1, 0, 0, 0, 0, 0 + ); + mListener3.assertValues( + 1, 0, 1, 0, 0, 0, 0, 0 + ); + } + + @Test + public void cancelCalledOnChildren() { + mRule.getScenario().onActivity((a) -> { + mSet1.start(); + mSet1.cancel(); + }); + waitForOnUiThread(() -> mListener1.endForward > 0); + + // only startForward and endForward should have been called once + mListener1.assertValues( + 1, 0, 1, 0, 1, 0, 0, 0 + ); + mListener2.assertValues( + 1, 0, 1, 0, 1, 0, 0, 0 + ); + mListener3.assertValues( + 1, 0, 1, 0, 1, 0, 0, 0 + ); + } + + @Test + public void startEndReversedCalledOnChildren() { + mRule.getScenario().onActivity((a) -> mSet1.reverse()); + waitForOnUiThread(() -> mListener1.endReverse > 0); + + // only startForward and endForward should have been called once + mListener1.assertValues( + 0, 1, 0, 1, 0, 0, 0, 0 + ); + mListener2.assertValues( + 0, 1, 0, 1, 0, 0, 0, 0 + ); + mListener3.assertValues( + 0, 1, 0, 1, 0, 0, 0, 0 + ); + } + + @Test + public void pauseResumeCalledOnChildren() { + mRule.getScenario().onActivity((a) -> { + mSet1.start(); + mSet1.pause(); + }); + waitForOnUiThread(() -> mListener1.pause > 0); + + // only startForward and pause should have been called once + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + mListener3.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + + mRule.getScenario().onActivity((a) -> mSet1.resume()); + waitForOnUiThread(() -> mListener1.endForward > 0); + + // resume and endForward should have been called once + mListener1.assertValues( + 1, 0, 1, 0, 0, 0, 1, 1 + ); + mListener2.assertValues( + 1, 0, 1, 0, 0, 0, 1, 1 + ); + mListener3.assertValues( + 1, 0, 1, 0, 0, 0, 1, 1 + ); + } + + @Test + public void updateOnlyWhileChangingValues() { + ArrayList<Float> updateValues = new ArrayList<>(); + mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateValues.add((Float) animation.getAnimatedValue()); + } + }); + + mSet1.setCurrentPlayTime(0); + + assertEquals(1, updateValues.size()); + assertEquals(0f, updateValues.get(0), 0f); + } + + @Test + public void updateOnlyWhileRunning() { + ArrayList<Float> updateValues = new ArrayList<>(); + mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateValues.add((Float) animation.getAnimatedValue()); + } + }); + + mRule.getScenario().onActivity((a) -> { + mSet1.start(); + }); + + waitForOnUiThread(() -> mListener1.endForward > 0); + + // the duration is only 1ms, so there should only be two values, 0 and 100. + assertEquals(0f, updateValues.get(0), 0f); + assertEquals(100f, updateValues.get(updateValues.size() - 1), 0f); + + // now check all the values in the middle, which can never go from 100->0. + boolean isAtEnd = false; + for (int i = 1; i < updateValues.size() - 1; i++) { + float actual = updateValues.get(i); + if (actual == 100f) { + isAtEnd = true; + } + float expected = isAtEnd ? 100f : 0f; + assertEquals(expected, actual, 0f); + } + } + + @Test + public void pauseResumeSeekingAnimators() { + ValueAnimator animator2 = ValueAnimator.ofFloat(0f, 1f); + mSet2.play(animator2).after(mAnimator); + mSet2.setStartDelay(100); + mSet1.setStartDelay(100); + mAnimator.setDuration(100); + + mActivity.runOnUiThread(() -> { + mSet1.setCurrentPlayTime(0); + mSet1.pause(); + + // only startForward and pause should have been called once + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + mListener2.assertValues( + 0, 0, 0, 0, 0, 0, 0, 0 + ); + mListener3.assertValues( + 0, 0, 0, 0, 0, 0, 0, 0 + ); + + mSet1.resume(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 1, 1 + ); + mListener2.assertValues( + 0, 0, 0, 0, 0, 0, 0, 0 + ); + mListener3.assertValues( + 0, 0, 0, 0, 0, 0, 0, 0 + ); + + mSet1.setCurrentPlayTime(200); + + // resume and endForward should have been called once + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 1, 1 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 0, 0 + ); + mListener3.assertValues( + 1, 0, 0, 0, 0, 0, 0, 0 + ); + + mSet1.pause(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 2, 1 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + mListener3.assertValues( + 1, 0, 0, 0, 0, 0, 1, 0 + ); + mSet1.resume(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 2, 2 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 1, 1 + ); + mListener3.assertValues( + 1, 0, 0, 0, 0, 0, 1, 1 + ); + + // now go to animator2 + mSet1.setCurrentPlayTime(400); + mSet1.pause(); + mSet1.resume(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 3, 3 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 2, 2 + ); + mListener3.assertValues( + 1, 0, 1, 0, 0, 0, 1, 1 + ); + + // now go back to mAnimator + mSet1.setCurrentPlayTime(250); + mSet1.pause(); + mSet1.resume(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 4, 4 + ); + mListener2.assertValues( + 1, 0, 0, 0, 0, 0, 3, 3 + ); + mListener3.assertValues( + 1, 1, 1, 0, 0, 0, 2, 2 + ); + + // now go back to before mSet2 was being run + mSet1.setCurrentPlayTime(1); + mSet1.pause(); + mSet1.resume(); + mListener1.assertValues( + 1, 0, 0, 0, 0, 0, 5, 5 + ); + mListener2.assertValues( + 1, 0, 0, 1, 0, 0, 3, 3 + ); + mListener3.assertValues( + 1, 1, 1, 1, 0, 0, 2, 2 + ); + }); + } + + @Test + public void endInCancel() throws Throwable { + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mSet1.end(); + } + }; + mSet1.addListener(listener); + mActivity.runOnUiThread(() -> { + mSet1.start(); + mSet1.cancel(); + // Should go to the end value + View square = mActivity.findViewById(R.id.square1); + assertEquals(100f, square.getTranslationX(), 0.001f); + }); + } + + @Test + public void reentrantStart() throws Throwable { + CountDownLatch latch = new CountDownLatch(3); + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation, boolean isReverse) { + mSet1.start(); + latch.countDown(); + } + }; + mSet1.addListener(listener); + mSet2.addListener(listener); + mAnimator.addListener(listener); + mActivity.runOnUiThread(() -> mSet1.start()); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread hasn't been destroyed by a stack overflow... + mActivity.runOnUiThread(() -> {}); + } + + @Test + public void reentrantEnd() throws Throwable { + CountDownLatch latch = new CountDownLatch(3); + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + mSet1.end(); + latch.countDown(); + } + }; + mSet1.addListener(listener); + mSet2.addListener(listener); + mAnimator.addListener(listener); + mActivity.runOnUiThread(() -> { + mSet1.start(); + mSet1.end(); + }); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread hasn't been destroyed by a stack overflow... + mActivity.runOnUiThread(() -> {}); + } + + @Test + public void reentrantPause() throws Throwable { + CountDownLatch latch = new CountDownLatch(3); + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationPause(Animator animation) { + mSet1.pause(); + latch.countDown(); + } + }; + mSet1.addPauseListener(listener); + mSet2.addPauseListener(listener); + mAnimator.addPauseListener(listener); + mActivity.runOnUiThread(() -> { + mSet1.start(); + mSet1.pause(); + }); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread hasn't been destroyed by a stack overflow... + mActivity.runOnUiThread(() -> {}); + } + + @Test + public void reentrantResume() throws Throwable { + CountDownLatch latch = new CountDownLatch(3); + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationResume(Animator animation) { + mSet1.resume(); + latch.countDown(); + } + }; + mSet1.addPauseListener(listener); + mSet2.addPauseListener(listener); + mAnimator.addPauseListener(listener); + mActivity.runOnUiThread(() -> { + mSet1.start(); + mSet1.pause(); + mSet1.resume(); + }); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread hasn't been destroyed by a stack overflow... + mActivity.runOnUiThread(() -> {}); + } + + private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) { + final boolean[] value = new boolean[1]; + PollingCheck.waitFor(() -> { + mActivity.runOnUiThread(() -> value[0] = condition.canProceed()); + return value[0]; + }); + } + + private static class CountListener implements Animator.AnimatorListener, + Animator.AnimatorPauseListener { + public int startNoParam; + public int endNoParam; + public int startReverse; + public int startForward; + public int endForward; + public int endReverse; + public int cancel; + public int repeat; + public int pause; + public int resume; + + public void assertValues( + int startForward, + int startReverse, + int endForward, + int endReverse, + int cancel, + int repeat, + int pause, + int resume + ) { + assertEquals("onAnimationStart() without direction", 0, startNoParam); + assertEquals("onAnimationEnd() without direction", 0, endNoParam); + assertEquals("onAnimationStart(forward)", startForward, this.startForward); + assertEquals("onAnimationStart(reverse)", startReverse, this.startReverse); + assertEquals("onAnimationEnd(forward)", endForward, this.endForward); + assertEquals("onAnimationEnd(reverse)", endReverse, this.endReverse); + assertEquals("onAnimationCancel()", cancel, this.cancel); + assertEquals("onAnimationRepeat()", repeat, this.repeat); + assertEquals("onAnimationPause()", pause, this.pause); + assertEquals("onAnimationResume()", resume, this.resume); + } + + @Override + public void onAnimationStart(Animator animation, boolean isReverse) { + if (isReverse) { + startReverse++; + } else { + startForward++; + } + } + + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + if (isReverse) { + endReverse++; + } else { + endForward++; + } + } + + @Override + public void onAnimationStart(Animator animation) { + startNoParam++; + } + + @Override + public void onAnimationEnd(Animator animation) { + endNoParam++; + } + + @Override + public void onAnimationCancel(Animator animation) { + cancel++; + } + + @Override + public void onAnimationRepeat(Animator animation) { + repeat++; + } + + @Override + public void onAnimationPause(Animator animation) { + pause++; + } + + @Override + public void onAnimationResume(Animator animation) { + resume++; + } + } +} diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java index dee0a3ecdbe0..a53d57f0383c 100644 --- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java +++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java @@ -40,6 +40,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @MediumTest @@ -1067,6 +1069,64 @@ public class ValueAnimatorTests { }); } + @Test + public void reentrantStart() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + a1.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation, boolean isReverse) { + a1.start(); + latch.countDown(); + } + }); + mActivityRule.runOnUiThread(() -> a1.start()); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread isn't blocked by an infinite loop: + mActivityRule.runOnUiThread(() -> {}); + } + + @Test + public void reentrantPause() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + a1.addPauseListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationPause(Animator animation) { + a1.pause(); + latch.countDown(); + } + }); + mActivityRule.runOnUiThread(() -> { + a1.start(); + a1.pause(); + }); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread isn't blocked by an infinite loop: + mActivityRule.runOnUiThread(() -> {}); + } + + @Test + public void reentrantResume() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + a1.addPauseListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationResume(Animator animation) { + a1.resume(); + latch.countDown(); + } + }); + mActivityRule.runOnUiThread(() -> { + a1.start(); + a1.pause(); + a1.resume(); + }); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + // Make sure that the UI thread isn't blocked by an infinite loop: + mActivityRule.runOnUiThread(() -> {}); + } + class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener { boolean wasRunning = false; long firstRunningFrameTime = -1; diff --git a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java index 81cd4da4f425..8cc88ea230a1 100644 --- a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java +++ b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java @@ -135,11 +135,15 @@ public class ViewPropertyAnimatorTest { * @throws Exception */ @Before - public void setUp() throws Exception { + public void setUp() throws Throwable { final BasicAnimatorActivity activity = mActivityRule.getActivity(); Button button = activity.findViewById(R.id.animatingButton); mAnimator = button.animate().x(100).y(100); + mActivityRule.runOnUiThread(() -> { + mAnimator.start(); + mAnimator.cancel(); + }); // mListener is the main testing mechanism of this file. The asserts of each test // are embedded in the listener callbacks that it implements. diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java new file mode 100644 index 000000000000..ee62d7528818 --- /dev/null +++ b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java @@ -0,0 +1,167 @@ +/* + * 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.internal.expresslog; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SmallTest +public class ScaledRangeOptionsTest { + private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName(); + + @Test + public void testGetBinsCount() { + Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2); + assertEquals(3, options1.getBinsCount()); + + Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2); + assertEquals(12, options10.getBinsCount()); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructZeroBinsCount() { + new Histogram.ScaledRangeOptions(0, 100, 100, 2); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructNegativeBinsCount() { + new Histogram.ScaledRangeOptions(-1, 100, 100, 2); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructNegativeFirstBinWidth() { + new Histogram.ScaledRangeOptions(10, 100, -100, 2); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructTooSmallFirstBinWidth() { + new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructNegativeScaleFactor() { + new Histogram.ScaledRangeOptions(10, 100, 100, -2); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructTooSmallScaleFactor() { + new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructTooBigScaleFactor() { + new Histogram.ScaledRangeOptions(10, 100, 100, 500.f); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructTooBigBinRange() { + new Histogram.ScaledRangeOptions(100, 100, 100, 10.f); + } + + @Test + public void testBinIndexForRangeEqual1() { + Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1); + assertEquals(12, options.getBinsCount()); + + assertEquals(11, options.getBinForSample(11)); + + for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { + assertEquals(i, options.getBinForSample(i)); + } + } + + @Test + public void testBinIndexForRangeEqual2() { + // this should produce bin otpions similar to linear histogram with bin width 2 + Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1); + assertEquals(12, options.getBinsCount()); + + for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { + assertEquals(i, options.getBinForSample(i * 2)); + assertEquals(i, options.getBinForSample(i * 2 - 1)); + } + } + + @Test + public void testBinIndexForRangeEqual5() { + Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1); + assertEquals(4, options.getBinsCount()); + for (int i = 0; i < 2; i++) { + for (int sample = 0; sample < 5; sample++) { + assertEquals(i + 1, options.getBinForSample(i * 5 + sample)); + } + } + } + + @Test + public void testBinIndexForRangeEqual10() { + Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1); + assertEquals(0, options.getBinForSample(0)); + assertEquals(options.getBinsCount() - 2, options.getBinForSample(100)); + assertEquals(options.getBinsCount() - 1, options.getBinForSample(101)); + + final float binSize = (101 - 1) / 10f; + for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) { + assertEquals(i, options.getBinForSample(i * binSize)); + } + } + + @Test + public void testBinIndexForScaleFactor2() { + final int binsCount = 10; + final int minValue = 10; + final int firstBinWidth = 5; + final int scaledFactor = 2; + + Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions( + binsCount, minValue, firstBinWidth, scaledFactor); + assertEquals(binsCount + 2, options.getBinsCount()); + long[] binCounts = new long[10]; + + // precalculate max valid value - start value for the overflow bin + int lastBinStartValue = minValue; //firstBinMin value + int lastBinWidth = firstBinWidth; + for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) { + lastBinStartValue = lastBinStartValue + lastBinWidth; + lastBinWidth *= scaledFactor; + } + + // underflow bin + for (int i = 1; i < minValue; i++) { + assertEquals(0, options.getBinForSample(i)); + } + + for (int i = 10; i < lastBinStartValue; i++) { + assertTrue(options.getBinForSample(i) > 0); + assertTrue(options.getBinForSample(i) <= binsCount); + binCounts[options.getBinForSample(i) - 1]++; + } + + // overflow bin + assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue)); + + for (int i = 1; i < binsCount; i++) { + assertEquals(binCounts[i], binCounts[i - 1] * 2L); + } + } +} diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java index 9fa6d0634fbe..037dbb32c2f8 100644 --- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java +++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java @@ -24,11 +24,11 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) +@SmallTest public class UniformOptionsTest { private static final String TAG = UniformOptionsTest.class.getSimpleName(); @Test - @SmallTest public void testGetBinsCount() { Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000); assertEquals(3, options1.getBinsCount()); @@ -38,25 +38,21 @@ public class UniformOptionsTest { } @Test(expected = IllegalArgumentException.class) - @SmallTest public void testConstructZeroBinsCount() { new Histogram.UniformOptions(0, 100, 1000); } @Test(expected = IllegalArgumentException.class) - @SmallTest public void testConstructNegativeBinsCount() { new Histogram.UniformOptions(-1, 100, 1000); } @Test(expected = IllegalArgumentException.class) - @SmallTest public void testConstructMaxValueLessThanMinValue() { new Histogram.UniformOptions(10, 1000, 100); } @Test - @SmallTest public void testBinIndexForRangeEqual1() { Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11); for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { @@ -65,7 +61,6 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual2() { Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21); for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { @@ -75,7 +70,6 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual5() { Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10); assertEquals(4, options.getBinsCount()); @@ -87,7 +81,6 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual10() { Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101); assertEquals(0, options.getBinForSample(0)); @@ -101,7 +94,6 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual90() { final int binCount = 10; final int minValue = 100; diff --git a/data/etc/com.android.intentresolver.xml b/data/etc/com.android.intentresolver.xml index f4e94ad0e04b..af6492609157 100644 --- a/data/etc/com.android.intentresolver.xml +++ b/data/etc/com.android.intentresolver.xml @@ -19,5 +19,6 @@ <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> + <permission name="android.permission.QUERY_CLONED_APPS"/> </privapp-permissions> </permissions> diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml new file mode 100644 index 000000000000..022594982ca3 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#bf309fb5" /> + <corners android:radius="20dp" /> + <stroke android:width="1dp" color="#A00080FF"/> +</shape> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index cb1a6e7ace6b..ac6e4c2a6521 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -59,7 +59,7 @@ public class TabletopModeController implements */ private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = SystemProperties.getBoolean( - "persist.wm.debug.enable_move_floating_window_in_tabletop", false); + "persist.wm.debug.enable_move_floating_window_in_tabletop", true); /** * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cc0da2840fa0..eb7c32fe8227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -671,13 +671,17 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellController shellController, + DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { - return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, - transitions, desktopModeTaskRepository, mainExecutor); + return new DesktopTasksController(context, shellInit, shellController, displayController, + shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions, + desktopModeTaskRepository, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index c9c0e40f616c..ad334b5f2dc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -41,7 +41,6 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.util.ArraySet; -import android.util.Pair; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.DisplayAreaInfo; @@ -364,10 +363,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request); - Pair<Transitions.TransitionHandler, WindowContainerTransaction> subHandler = - mTransitions.dispatchRequest(transition, request, this); - WindowContainerTransaction wct = subHandler != null - ? subHandler.second : new WindowContainerTransaction(); + WindowContainerTransaction wct = new WindowContainerTransaction(); bringDesktopAppsToFront(wct); wct.reorder(request.getTriggerTask().token, true /* onTop */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java new file mode 100644 index 000000000000..015d5c1705e7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -0,0 +1,227 @@ +/* + * 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.wm.shell.desktopmode; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; + +import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Animated visual indicator for Desktop Mode windowing transitions. + */ +public class DesktopModeVisualIndicator { + + private final Context mContext; + private final DisplayController mDisplayController; + private final ShellTaskOrganizer mTaskOrganizer; + private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer; + private final ActivityManager.RunningTaskInfo mTaskInfo; + private final SurfaceControl mTaskSurface; + private SurfaceControl mLeash; + + private final SyncTransactionQueue mSyncQueue; + private SurfaceControlViewHost mViewHost; + + public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue, + ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController, + Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer, + RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) { + mSyncQueue = syncQueue; + mTaskInfo = taskInfo; + mDisplayController = displayController; + mContext = context; + mTaskSurface = taskSurface; + mTaskOrganizer = taskOrganizer; + mRootTdaOrganizer = taskDisplayAreaOrganizer; + } + + /** + * Create and animate the indicator for the exit desktop mode transition. + */ + public void createFullscreenIndicator() { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final Resources resources = mContext.getResources(); + final DisplayMetrics metrics = resources.getDisplayMetrics(); + final int screenWidth = metrics.widthPixels; + final int screenHeight = metrics.heightPixels; + final int padding = mDisplayController + .getDisplayLayout(mTaskInfo.displayId).stableInsets().top; + final ImageView v = new ImageView(mContext); + v.setImageResource(R.drawable.desktop_windowing_transition_background); + final SurfaceControl.Builder builder = new SurfaceControl.Builder(); + mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); + mLeash = builder + .setName("Fullscreen Indicator") + .setContainerLayer() + .build(); + t.show(mLeash); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(screenWidth, screenHeight, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); + lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + final WindowlessWindowManager windowManager = new WindowlessWindowManager( + mTaskInfo.configuration, mLeash, + null /* hostInputToken */); + mViewHost = new SurfaceControlViewHost(mContext, + mDisplayController.getDisplay(mTaskInfo.displayId), windowManager, + "FullscreenVisualIndicator"); + mViewHost.setView(v, lp); + // We want this indicator to be behind the dragged task, but in front of all others. + t.setRelativeLayer(mLeash, mTaskSurface, -1); + + mSyncQueue.runInSync(transaction -> { + transaction.merge(t); + t.close(); + }); + final Rect startBounds = new Rect(padding, padding, + screenWidth - padding, screenHeight - padding); + final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v, + startBounds); + animator.start(); + } + + /** + * Release the indicator and its components when it is no longer needed. + */ + public void releaseFullscreenIndicator() { + if (mViewHost == null) return; + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mLeash != null) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.remove(mLeash); + mLeash = null; + mSyncQueue.runInSync(transaction -> { + transaction.merge(t); + t.close(); + }); + } + } + /** + * Animator for Desktop Mode transitions which supports bounds and alpha animation. + */ + private static class VisualIndicatorAnimator extends ValueAnimator { + private static final int FULLSCREEN_INDICATOR_DURATION = 200; + private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f; + private static final float INDICATOR_FINAL_OPACITY = 0.7f; + + private final ImageView mView; + private final Rect mStartBounds; + private final Rect mEndBounds; + private final RectEvaluator mRectEvaluator; + + private VisualIndicatorAnimator(ImageView view, Rect startBounds, + Rect endBounds) { + mView = view; + mStartBounds = new Rect(startBounds); + mEndBounds = endBounds; + setFloatValues(0, 1); + mRectEvaluator = new RectEvaluator(new Rect()); + } + + /** + * Create animator for visual indicator of fullscreen transition + * + * @param view the view for this indicator + * @param startBounds the starting bounds of the fullscreen indicator + */ + public static VisualIndicatorAnimator fullscreenIndicator(ImageView view, + Rect startBounds) { + view.getDrawable().setBounds(startBounds); + int width = startBounds.width(); + int height = startBounds.height(); + Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)), + (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)), + (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)), + (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height))); + VisualIndicatorAnimator animator = new VisualIndicatorAnimator( + view, startBounds, endBounds); + animator.setInterpolator(new DecelerateInterpolator()); + setupFullscreenIndicatorAnimation(animator); + return animator; + } + + /** + * Add necessary listener for animation of fullscreen indicator + */ + private static void setupFullscreenIndicatorAnimation( + VisualIndicatorAnimator animator) { + animator.addUpdateListener(a -> { + if (animator.mView != null) { + animator.updateBounds(a.getAnimatedFraction(), animator.mView); + animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView); + } else { + animator.cancel(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animator.mView.getDrawable().setBounds(animator.mEndBounds); + } + }); + animator.setDuration(FULLSCREEN_INDICATOR_DURATION); + } + + /** + * Update bounds of view based on current animation fraction. + * Use of delta is to animate bounds independently, in case we need to + * run multiple animations simultaneously. + * + * @param fraction fraction to use, compared against previous fraction + * @param view the view to update + */ + private void updateBounds(float fraction, ImageView view) { + Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds); + view.getDrawable().setBounds(currentBounds); + } + + /** + * Fade in the fullscreen indicator + * + * @param fraction current animation fraction + */ + private void updateIndicatorAlpha(float fraction, View view) { + view.setAlpha(fraction * INDICATOR_FINAL_OPACITY); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 31c5e33f21e3..5696dfc5069e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager +import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -37,11 +38,14 @@ import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ExecutorUtils import com.android.wm.shell.common.ExternalInterfaceBinder import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener @@ -55,16 +59,20 @@ import java.util.function.Consumer /** Handles moving tasks in and out of desktop */ class DesktopTasksController( - private val context: Context, - shellInit: ShellInit, - private val shellController: ShellController, - private val shellTaskOrganizer: ShellTaskOrganizer, - private val transitions: Transitions, - private val desktopModeTaskRepository: DesktopModeTaskRepository, - @ShellMainThread private val mainExecutor: ShellExecutor + private val context: Context, + shellInit: ShellInit, + private val shellController: ShellController, + private val displayController: DisplayController, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val syncQueue: SyncTransactionQueue, + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + private val transitions: Transitions, + private val desktopModeTaskRepository: DesktopModeTaskRepository, + @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler { private val desktopMode: DesktopModeImpl + private var visualIndicator: DesktopModeVisualIndicator? = null init { desktopMode = DesktopModeImpl() @@ -298,6 +306,52 @@ class DesktopTasksController( } /** + * Perform checks required on drag move. Create/release fullscreen indicator as needed. + * + * @param taskInfo the task being dragged. + * @param taskSurface SurfaceControl of dragged task. + * @param y coordinate of dragged task. Used for checks against status bar height. + */ + fun onDragPositioningMove( + taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, + y: Float + ) { + val statusBarHeight = displayController + .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 + if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + if (y <= statusBarHeight && visualIndicator == null) { + visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, + displayController, context, taskSurface, shellTaskOrganizer, + rootTaskDisplayAreaOrganizer) + visualIndicator?.createFullscreenIndicator() + } else if (y > statusBarHeight && visualIndicator != null) { + visualIndicator?.releaseFullscreenIndicator() + visualIndicator = null + } + } + } + + /** + * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area. + * + * @param taskInfo the task being dragged. + * @param y height of drag, to be checked against status bar height. + */ + fun onDragPositioningEnd( + taskInfo: RunningTaskInfo, + y: Float + ) { + val statusBarHeight = displayController + .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 + if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + moveToFullscreen(taskInfo.taskId) + visualIndicator?.releaseFullscreenIndicator() + visualIndicator = null + } + } + + /** * Adds a listener to find out about changes in the visibility of freeform tasks. * * @param listener the listener to add. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 748f4a190b00..582616d99954 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -163,6 +163,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb this::onKeepClearAreasChangedCallback; private void onKeepClearAreasChangedCallback() { + if (mIsKeyguardShowingOrAnimating) { + // early bail out if the change was caused by keyguard showing up + return; + } if (!mEnablePipKeepClearAlgorithm) { // early bail out if the keep clear areas feature is disabled return; @@ -188,6 +192,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb // early bail out if the keep clear areas feature is disabled return; } + if (mIsKeyguardShowingOrAnimating) { + // early bail out if the change was caused by keyguard showing up + return; + } // only move if we're in PiP or transitioning into PiP if (!mPipTransitionState.shouldBlockResizeRequest()) { Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState, @@ -639,9 +647,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayLayout pendingLayout = mDisplayController .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()); if (mIsInFixedRotation + || mIsKeyguardShowingOrAnimating || pendingLayout.rotation() != mPipBoundsState.getDisplayLayout().rotation()) { - // bail out if there is a pending rotation or fixed rotation change + // bail out if there is a pending rotation or fixed rotation change or + // there's a keyguard present return; } int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; @@ -936,10 +946,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.getDisplayBounds().right, mPipBoundsState.getDisplayBounds().bottom); mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect); - updatePipPositionForKeepClearAreas(); } else { mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG); } + updatePipPositionForKeepClearAreas(); } private void setLauncherAppIconSize(int iconSizePx) { 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 def945e53f9b..33cbdac67061 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 @@ -1335,7 +1335,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */); mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); - setRootForceTranslucent(true, wct); + setRootForceTranslucent(true, finishedWCT); finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); mSyncQueue.queue(finishedWCT); mSyncQueue.runInSync(at -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index e643170273de..e632b56d5e54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -31,7 +31,6 @@ import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; import android.graphics.Color; -import android.graphics.ColorSpace; import android.graphics.Matrix; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -162,13 +161,12 @@ class ScreenRotationAnimation { .setName("RotationLayer") .build(); - final ColorSpace colorSpace = screenshotBuffer.getColorSpace(); + TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer); final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); - t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace()); - t.setBuffer(mScreenshotLayer, hardwareBuffer); t.show(mScreenshotLayer); if (!isCustomRotate()) { - mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace); + mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, + screenshotBuffer.getColorSpace()); } hardwareBuffer.close(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index d2a80471a46f..317b9a322fbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -300,7 +300,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } case MotionEvent.ACTION_MOVE: { + final DesktopModeWindowDecoration decoration = + mWindowDecorByTaskId.get(mTaskId); final int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, + decoration.mTaskSurface, e.getRawY(dragPointerIdx))); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; @@ -309,18 +313,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { final int dragPointerIdx = e.findPointerIndex(mDragPointerId); - final int statusBarHeight = mDisplayController - .getDisplayLayout(taskInfo.displayId).stableInsets().top; mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); - if (e.getRawY(dragPointerIdx) <= statusBarHeight) { - if (DesktopModeStatus.isProto2Enabled() - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // Switch a single task to fullscreen - mDesktopTasksController.ifPresent( - c -> c.moveToFullscreen(taskInfo)); - } - } + mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, + e.getRawY(dragPointerIdx))); final boolean wasDragging = mIsDragging; mIsDragging = false; return wasDragging; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 43605e30b813..a6a4d1d2cc72 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -297,7 +297,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY; } mHandleMenuPosition.set(x, y); - String namePrefix = "Caption Menu"; + final String namePrefix = "Caption Menu"; mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y, menuWidth, menuHeight, shadowRadius, cornerRadius); mSyncQueue.runInSync(transaction -> { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 43f8f7b074bf..63de74fa3b05 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager.RunningTaskInfo; @@ -418,6 +419,17 @@ public class DesktopModeControllerTest extends ShellTestCase { assertThat(wct).isNotNull(); } + @Test + public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() { + RunningTaskInfo trigger = new RunningTaskInfo(); + trigger.token = new MockToken().token(); + trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + mController.handleRequest( + mock(IBinder.class), + new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */)); + verifyZeroInteractions(mTransitions); + } + private DesktopModeController createController() { return new DesktopModeController(mContext, mShellInit, mShellController, mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 95e78a8b7bcc..5cad50da7e42 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -37,11 +37,14 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask @@ -73,7 +76,10 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var shellController: ShellController + @Mock lateinit var displayController: DisplayController @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock lateinit var syncQueue: SyncTransactionQueue + @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock lateinit var transitions: Transitions lateinit var mockitoSession: StaticMockitoSession @@ -105,7 +111,10 @@ class DesktopTasksControllerTest : ShellTestCase() { context, shellInit, shellController, + displayController, shellTaskOrganizer, + syncQueue, + rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, TestShellExecutor() diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 41ced8cebf83..139cdde5c0d2 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -54,6 +54,7 @@ struct MemoryPolicy { // collection bool purgeScratchOnly = true; // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped + // WARNING: Enabling this option can lead to instability, see b/266626090 bool releaseContextOnStoppedOnly = false; }; diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index e8e39741c3bd..5d72424c8f8a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -38,7 +38,9 @@ import com.android.credentialmanager.common.StartBalIntentSenderForResultContrac import com.android.credentialmanager.createflow.CreateCredentialScreen import com.android.credentialmanager.createflow.hasContentToDisplay import com.android.credentialmanager.getflow.GetCredentialScreen +import com.android.credentialmanager.getflow.GetGenericCredentialScreen import com.android.credentialmanager.getflow.hasContentToDisplay +import com.android.credentialmanager.getflow.isFallbackScreen import com.android.credentialmanager.ui.theme.PlatformTheme @ExperimentalMaterialApi @@ -118,11 +120,19 @@ class CredentialSelectorActivity : ComponentActivity() { providerActivityLauncher = launcher ) } else if (getCredentialUiState != null && hasContentToDisplay(getCredentialUiState)) { - GetCredentialScreen( - viewModel = viewModel, - getCredentialUiState = getCredentialUiState, - providerActivityLauncher = launcher - ) + if (isFallbackScreen(getCredentialUiState)) { + GetGenericCredentialScreen( + viewModel = viewModel, + getCredentialUiState = getCredentialUiState, + providerActivityLauncher = launcher + ) + } else { + GetCredentialScreen( + viewModel = viewModel, + getCredentialUiState = getCredentialUiState, + providerActivityLauncher = launcher + ) + } } else { Log.d(Constants.LOG_TAG, "UI wasn't able to render neither get nor create flow") reportInstantiationErrorAndFinishActivity(credManRepo) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index bcf692fceacc..7b98049b51c0 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager.createflow +import android.text.TextUtils import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest @@ -668,7 +669,7 @@ fun PrimaryCreateOptionRow( entryHeadlineText = requestDisplayInfo.title, entrySecondLineText = when (requestDisplayInfo.type) { CredentialType.PASSKEY -> { - if (requestDisplayInfo.subtitle != null) { + if (!TextUtils.isEmpty(requestDisplayInfo.subtitle)) { requestDisplayInfo.subtitle + " • " + stringResource( R.string.passkey_before_subtitle ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt new file mode 100644 index 000000000000..8b95b5e46aa1 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt @@ -0,0 +1,32 @@ +/* + * 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.credentialmanager.getflow + +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.result.ActivityResult +import androidx.activity.result.IntentSenderRequest +import androidx.compose.runtime.Composable +import com.android.credentialmanager.CredentialSelectorViewModel + +@Composable +fun GetGenericCredentialScreen( + viewModel: CredentialSelectorViewModel, + getCredentialUiState: GetCredentialUiState, + providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult> +) { + // TODO(b/274129098): Implement Screen for mDocs +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 263a632ef5ee..7a8679038579 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -41,6 +41,10 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean { !state.requestDisplayInfo.preferImmediatelyAvailableCredentials) } +internal fun isFallbackScreen(state: GetCredentialUiState): Boolean { + return false +} + internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? { if (providerDisplayInfo.authenticationEntryList.isNotEmpty()) { return null diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 071ab27f60b9..a9d15f3b4afe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -97,7 +97,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { @Retention(RetentionPolicy.SOURCE) @IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE, - SELECTION_BEHAVIOR_TRANSFER, + SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER, SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP }) public @interface SelectionBehavior { diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 4f7d09963fc6..4d6c2022c3b8 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -321,7 +321,8 @@ asked for it --> <RelativeLayout android:id="@+id/bottom_buttons" android:layout_width="match_parent" - android:layout_height="60dp" + android:layout_height="wrap_content" + android:minHeight="60dp" android:gravity="center_vertical" android:paddingStart="4dp" android:paddingEnd="4dp" diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 873a695ecd93..64a9cc995248 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -16,6 +16,10 @@ package com.android.systemui; +import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X; +import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_Y; +import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; + import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; import android.animation.Animator; @@ -40,6 +44,7 @@ import android.view.accessibility.AccessibilityEvent; import androidx.annotation.VisibleForTesting; +import com.android.internal.dynamicanimation.animation.SpringForce; import com.android.systemui.animation.Interpolators; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -47,14 +52,14 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.wm.shell.animation.FlingAnimationUtils; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.animation.PhysicsAnimator.SpringConfig; import java.util.function.Consumer; public class SwipeHelper implements Gefingerpoken { static final String TAG = "com.android.systemui.SwipeHelper"; - private static final boolean DEBUG = false; private static final boolean DEBUG_INVALIDATE = false; - private static final boolean SLOW_ANIMATIONS = false; // DEBUG; private static final boolean CONSTRAIN_SWIPE = true; private static final boolean FADE_OUT_DURING_SWIPE = true; private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; @@ -66,7 +71,6 @@ public class SwipeHelper implements Gefingerpoken { private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec - private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width // beyond which swipe progress->0 @@ -78,6 +82,9 @@ public class SwipeHelper implements Gefingerpoken { private float mMinSwipeProgress = 0f; private float mMaxSwipeProgress = 1f; + private final SpringConfig mSnapBackSpringConfig = + new SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + private final FlingAnimationUtils mFlingAnimationUtils; private float mPagingTouchSlop; private final float mSlopMultiplier; @@ -188,23 +195,27 @@ public class SwipeHelper implements Gefingerpoken { vt.getYVelocity(); } - protected ObjectAnimator createTranslationAnimation(View v, float newPos) { - ObjectAnimator anim = ObjectAnimator.ofFloat(v, - mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos); - return anim; - } + protected Animator getViewTranslationAnimator(View view, float target, + AnimatorUpdateListener listener) { + + cancelSnapbackAnimation(view); - private float getPerpendicularVelocity(VelocityTracker vt) { - return mSwipeDirection == X ? vt.getYVelocity() : - vt.getXVelocity(); + if (view instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) view).getTranslateViewAnimator(target, listener); + } + + return createTranslationAnimation(view, target, listener); } - protected Animator getViewTranslationAnimator(View v, float target, + protected Animator createTranslationAnimation(View view, float newPos, AnimatorUpdateListener listener) { - ObjectAnimator anim = createTranslationAnimation(v, target); + ObjectAnimator anim = ObjectAnimator.ofFloat(view, + mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos); + if (listener != null) { anim.addUpdateListener(listener); } + return anim; } @@ -327,6 +338,7 @@ public class SwipeHelper implements Gefingerpoken { mTouchedView = mCallback.getChildAtPosition(ev); if (mTouchedView != null) { + cancelSnapbackAnimation(mTouchedView); onDownUpdate(mTouchedView, ev); mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mTouchedView); mVelocityTracker.addMovement(ev); @@ -526,47 +538,59 @@ public class SwipeHelper implements Gefingerpoken { } /** - * After snapChild() and related animation finished, this function will be called. + * Starts a snapback animation and cancels any previous translate animations on the given view. + * + * @param animView view to animate + * @param targetLeft the end position of the translation + * @param velocity the initial velocity of the animation */ - protected void onSnapChildWithAnimationFinished() {} - - public void snapChild(final View animView, final float targetLeft, float velocity) { + protected void snapChild(final View animView, final float targetLeft, float velocity) { final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); - AnimatorUpdateListener updateListener = animation -> onTranslationUpdate(animView, - (float) animation.getAnimatedValue(), canBeDismissed); - Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener); - if (anim == null) { - onSnapChildWithAnimationFinished(); - return; - } - anim.addListener(new AnimatorListenerAdapter() { - boolean wasCancelled = false; + cancelTranslateAnimation(animView); - @Override - public void onAnimationCancel(Animator animator) { - wasCancelled = true; - } + PhysicsAnimator<? extends View> anim = + createSnapBackAnimation(animView, targetLeft, velocity); + anim.addUpdateListener((target, values) -> { + onTranslationUpdate(target, getTranslation(target), canBeDismissed); + }); + anim.addEndListener((t, p, wasFling, cancelled, finalValue, finalVelocity, allEnded) -> { + mSnappingChild = false; - @Override - public void onAnimationEnd(Animator animator) { - mSnappingChild = false; - if (!wasCancelled) { - updateSwipeProgressFromOffset(animView, canBeDismissed); - resetSwipeState(); - } - onSnapChildWithAnimationFinished(); + if (!cancelled) { + updateSwipeProgressFromOffset(animView, canBeDismissed); + resetSwipeState(); } + onChildSnappedBack(animView, targetLeft); }); - prepareSnapBackAnimation(animView, anim); mSnappingChild = true; - float maxDistance = Math.abs(targetLeft - getTranslation(animView)); - mFlingAnimationUtils.apply(anim, getTranslation(animView), targetLeft, velocity, - maxDistance); anim.start(); - mCallback.onChildSnappedBack(animView, targetLeft); } + private PhysicsAnimator<? extends View> createSnapBackAnimation(View target, float toPosition, + float startVelocity) { + if (target instanceof ExpandableNotificationRow) { + return PhysicsAnimator.getInstance((ExpandableNotificationRow) target).spring( + createFloatPropertyCompat(ExpandableNotificationRow.TRANSLATE_CONTENT), + toPosition, + startVelocity, + mSnapBackSpringConfig); + } + return PhysicsAnimator.getInstance(target).spring( + mSwipeDirection == X ? TRANSLATION_X : TRANSLATION_Y, toPosition, startVelocity, + mSnapBackSpringConfig); + } + + private void cancelTranslateAnimation(View animView) { + if (animView instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) animView).cancelTranslateAnimation(); + } + cancelSnapbackAnimation(animView); + } + + private void cancelSnapbackAnimation(View target) { + PhysicsAnimator.getInstance(target).cancel(); + } /** * Called to update the content alpha while the view is swiped @@ -576,17 +600,10 @@ public class SwipeHelper implements Gefingerpoken { } /** - * Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have - * to tell us what to do + * Called after {@link #snapChild(View, float, float)} and its related animation has finished. */ protected void onChildSnappedBack(View animView, float targetLeft) { - } - - /** - * Called to update the snap back animation. - */ - protected void prepareSnapBackAnimation(View view, Animator anim) { - // Do nothing + mCallback.onChildSnappedBack(animView, targetLeft); } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 46e945ba6880..3f22f18f6fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -103,11 +103,6 @@ constructor( } } - override fun onInit() { - mView.setAlphaInDuration(sysuiContext.resources.getInteger( - R.integer.auth_ripple_alpha_in_duration).toLong()) - } - @VisibleForTesting public override fun onViewAttached() { authController.addCallback(authControllerCallback) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 84094626193d..b0071340cf1a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -54,12 +54,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at private var lockScreenColorVal = Color.WHITE private val fadeDuration = 83L private val retractDuration = 400L - private var alphaInDuration: Long = 0 private val dwellShader = DwellRippleShader() private val dwellPaint = Paint() private val rippleShader = RippleShader() private val ripplePaint = Paint() - private var unlockedRippleAnimator: AnimatorSet? = null + private var unlockedRippleAnimator: Animator? = null private var fadeDwellAnimator: Animator? = null private var retractDwellAnimator: Animator? = null private var dwellPulseOutAnimator: Animator? = null @@ -85,12 +84,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } init { - rippleShader.color = 0xffffffff.toInt() // default color rippleShader.rawProgress = 0f rippleShader.pixelDensity = resources.displayMetrics.density rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH updateRippleFadeParams() ripplePaint.shader = rippleShader + setLockScreenColor(0xffffffff.toInt()) // default color dwellShader.color = 0xffffffff.toInt() // default color dwellShader.progress = 0f @@ -111,10 +110,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at dwellRadius = sensorRadius * 1.5f } - fun setAlphaInDuration(duration: Long) { - alphaInDuration = duration - } - /** * Animate dwell ripple inwards back to radius 0 */ @@ -253,7 +248,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator?) { drawDwell = false - resetRippleAlpha() } }) start() @@ -277,22 +271,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } } - val alphaInAnimator = ValueAnimator.ofInt(0, 62).apply { - duration = alphaInDuration - addUpdateListener { animator -> - rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - animator.animatedValue as Int - ) - invalidate() - } - } - - unlockedRippleAnimator = AnimatorSet().apply { - playTogether( - rippleAnimator, - alphaInAnimator - ) + unlockedRippleAnimator = rippleAnimator.apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { drawRipple = true @@ -310,17 +289,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at unlockedRippleAnimator?.start() } - fun resetRippleAlpha() { - rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - 255 - ) - } - fun setLockScreenColor(color: Int) { lockScreenColorVal = color - rippleShader.color = lockScreenColorVal - resetRippleAlpha() + rippleShader.color = ColorUtils.setAlphaComponent( + lockScreenColorVal, + 62 + ) } fun updateDwellRippleColor(isDozing: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java index 0ed7d2711c62..e9daa462c022 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java @@ -41,6 +41,7 @@ class ClipboardToast extends Toast.Callback { } mCopiedToast = Toast.makeText(mContext, R.string.clipboard_overlay_text_copied, Toast.LENGTH_SHORT); + mCopiedToast.addCallback(this); mCopiedToast.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index fc3263fd3d11..f0aefb5bc0df 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -398,7 +398,8 @@ public class DozeMachine { } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING || mState == State.DOZE_AOD || mState == State.DOZE - || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) { + || mState == State.DOZE_AOD_DOCKED || mState == State.DOZE_SUSPEND_TRIGGERS) + && requestedState == State.DOZE_PULSE_DONE) { Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); return mState; } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 8f3f64fbe50a..b4f24a92f925 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -251,20 +251,12 @@ object Flags { // TODO(b/270223352): Tracking Bug @JvmField val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = - unreleasedFlag( - 404, - "hide_smartspace_on_dream_overlay", - teamfood = true - ) + releasedFlag(404, "hide_smartspace_on_dream_overlay") // TODO(b/271460958): Tracking Bug @JvmField val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = - unreleasedFlag( - 405, - "show_weather_complication_on_dream_overlay", - teamfood = true - ) + releasedFlag(405, "show_weather_complication_on_dream_overlay") // 500 - quick settings @@ -517,7 +509,7 @@ object Flags { @JvmField val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP = sysPropBooleanFlag( - 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false) + 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true) // 1200 - predictive back @Keep diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 3e52ff2c2da0..9ab2e9922531 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -22,6 +22,7 @@ import android.animation.ValueAnimator import android.content.Context import android.graphics.Matrix import android.graphics.Rect +import android.os.DeadObjectException import android.os.Handler import android.os.PowerManager import android.os.RemoteException @@ -524,10 +525,22 @@ class KeyguardUnlockAnimationController @Inject constructor( surfaceBehindAlpha = 1f setSurfaceBehindAppearAmount(1f) - launcherUnlockController?.playUnlockAnimation( - true, - UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY, - 0 /* startDelay */) + try { + launcherUnlockController?.playUnlockAnimation( + true, + UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY, + 0 /* startDelay */) + } catch (e: DeadObjectException) { + // Hello! If you are here investigating a bug where Launcher is blank (no icons) + // then the below assumption about Launcher's destruction was incorrect. This + // would mean prepareToUnlock was called (blanking Launcher in preparation for + // the beginning of the unlock animation), but then somehow we were unable to + // call playUnlockAnimation to animate the icons back in. + Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " + + "Catching exception as this should mean Launcher is in the process " + + "of being destroyed, but the IPC to System UI telling us hasn't " + + "arrived yet.") + } launcherPreparedForUnlock = false } else { @@ -604,11 +617,23 @@ class KeyguardUnlockAnimationController @Inject constructor( private fun unlockToLauncherWithInWindowAnimations() { setSurfaceBehindAppearAmount(1f) - // Begin the animation, waiting for the shade to animate out. - launcherUnlockController?.playUnlockAnimation( - true /* unlocked */, - LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */, - CANNED_UNLOCK_START_DELAY /* startDelay */) + try { + // Begin the animation, waiting for the shade to animate out. + launcherUnlockController?.playUnlockAnimation( + true /* unlocked */, + LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */, + CANNED_UNLOCK_START_DELAY /* startDelay */) + } catch (e: DeadObjectException) { + // Hello! If you are here investigating a bug where Launcher is blank (no icons) + // then the below assumption about Launcher's destruction was incorrect. This + // would mean prepareToUnlock was called (blanking Launcher in preparation for + // the beginning of the unlock animation), but then somehow we were unable to + // call playUnlockAnimation to animate the icons back in. + Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " + + "Catching exception as this should mean Launcher is in the process " + + "of being destroyed, but the IPC to System UI telling us hasn't " + + "arrived yet.") + } launcherPreparedForUnlock = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index b10aa90e6d7f..e65c8a16f9e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -303,6 +303,10 @@ constructor( /** Tell the bouncer to start the pre hide animation. */ fun startDisappearAnimation(runnable: Runnable) { + if (willRunDismissFromKeyguard()) { + runnable.run() + return + } val finishRunnable = Runnable { runnable.run() repository.setPrimaryStartDisappearAnimation(null) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index b23247c30256..df93d235245c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -81,9 +81,7 @@ constructor( ) .map { if (willRunDismissFromKeyguard) { - ScrimAlpha( - notificationsAlpha = 1f, - ) + ScrimAlpha() } else if (leaveShadeOpen) { ScrimAlpha( behindAlpha = 1f, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index b71a91934314..6cf297c4885c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -16,10 +16,6 @@ package com.android.systemui.media.dialog; -import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED; -import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED; -import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED; - import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; @@ -535,11 +531,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @DoNotInline static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device, Context context) { - switch (device.getSubtext()) { - case SUBTEXT_AD_ROUTING_DISALLOWED: - case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED: + switch (device.getSelectionBehavior()) { + case SELECTION_BEHAVIOR_NONE: return context.getDrawable(R.drawable.media_output_status_failed); - case SUBTEXT_SUBSCRIPTION_REQUIRED: + case SELECTION_BEHAVIOR_TRANSFER: + return null; + case SELECTION_BEHAVIOR_GO_TO_APP: return context.getDrawable(R.drawable.media_output_status_help); } return null; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index f76f049abf97..f92a5abdbf23 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -192,8 +192,11 @@ public abstract class MediaOutputBaseAdapter extends mSubTitleText.setTextColor(mController.getColorItemContent()); mTwoLineTitleText.setTextColor(mController.getColorItemContent()); if (mController.isAdvancedLayoutSupported()) { - mIconAreaLayout.setOnClickListener(null); mVolumeValueText.setTextColor(mController.getColorItemContent()); + mTitleIcon.setOnTouchListener(((v, event) -> { + mSeekBar.dispatchTouchEvent(event); + return false; + })); } mSeekBar.setProgressTintList( ColorStateList.valueOf(mController.getColorSeekbarProgress())); @@ -444,9 +447,6 @@ public abstract class MediaOutputBaseAdapter extends } void updateIconAreaClickListener(View.OnClickListener listener) { - if (mController.isAdvancedLayoutSupported()) { - mIconAreaLayout.setOnClickListener(listener); - } mTitleIcon.setOnClickListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java index 253c3c713485..be5d60799f79 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java @@ -26,6 +26,7 @@ import android.widget.SeekBar; */ public class MediaOutputSeekbar extends SeekBar { private static final int SCALE_SIZE = 1000; + private static final int INITIAL_PROGRESS = 500; public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000; public MediaOutputSeekbar(Context context, AttributeSet attrs) { @@ -38,7 +39,7 @@ public class MediaOutputSeekbar extends SeekBar { } static int scaleVolumeToProgress(int volume) { - return volume * SCALE_SIZE; + return volume == 0 ? 0 : INITIAL_PROGRESS + volume * SCALE_SIZE; } int getVolume() { @@ -46,7 +47,7 @@ public class MediaOutputSeekbar extends SeekBar { } void setVolume(int volume) { - setProgress(volume * SCALE_SIZE, true); + setProgress(scaleVolumeToProgress(volume), true); } void setMaxVolume(int maxVolume) { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt index 9bccb7df4ed0..89f66b7daaf8 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt @@ -19,12 +19,11 @@ package com.android.systemui.mediaprojection.appselector.view import android.content.Context import android.content.res.Configuration import android.graphics.Rect +import android.view.WindowInsets.Type import android.view.WindowManager -import com.android.internal.R as AndroidR import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen -import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -62,17 +61,12 @@ constructor( val width = windowMetrics.bounds.width() var height = maximumWindowHeight - // TODO(b/271410803): Read isTransientTaskbar from Launcher val isLargeScreen = isLargeScreen(context) - val isTransientTaskbar = - QuickStepContract.isGesturalMode( - context.resources.getInteger( - com.android.internal.R.integer.config_navBarInteractionMode - ) - ) - if (isLargeScreen && !isTransientTaskbar) { + if (isLargeScreen) { val taskbarSize = - context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) + windowManager.currentWindowMetrics.windowInsets + .getInsets(Type.tappableElement()) + .bottom height -= taskbarSize } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index cfcc6713eca9..84d23c6a6f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -18,7 +18,7 @@ package com.android.systemui.navigationbar.gestural; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static com.android.systemui.classifier.Classifier.BACK_GESTURE; -import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent; +import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; import android.annotation.NonNull; import android.app.ActivityManager; @@ -244,7 +244,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsBackGestureAllowed; private boolean mGestureBlockingActivityRunning; private boolean mIsNewBackAffordanceEnabled; - private boolean mIsTrackpadGestureBackEnabled; + private boolean mIsTrackpadGestureFeaturesEnabled; private boolean mIsButtonForceVisible; private InputMonitor mInputMonitor; @@ -590,7 +590,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // Add a nav bar panel window mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE); - mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled( + mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled( Flags.TRACKPAD_GESTURE_FEATURES); resetEdgeBackPlugin(); mPluginManager.addPluginListener( @@ -883,7 +883,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void onMotionEvent(MotionEvent ev) { - boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev); + boolean isTrackpadEvent = isTrackpadThreeFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev); int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { if (DEBUG_MISSING_GESTURE) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java index 1345c9bdfeb3..9e2b6d3cd898 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java @@ -22,9 +22,10 @@ import android.view.MotionEvent; public final class Utilities { - public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled, + public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled, MotionEvent event) { - return isTrackpadGestureBackEnabled - && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE; + return isTrackpadGestureFeaturesEnabled + && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE + && event.getPointerCount() == 3; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index df1c8dfdde96..08fe2709b810 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -18,24 +18,26 @@ package com.android.systemui.qs.tiles; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; +import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.UserManager; import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.Switch; -import androidx.annotation.Nullable; - import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; @@ -50,6 +52,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -60,8 +63,14 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); + private static final String TAG = BluetoothTile.class.getSimpleName(); + private final BluetoothController mController; + private CachedBluetoothDevice mMetadataRegisteredDevice = null; + + private final Executor mExecutor; + @Inject public BluetoothTile( QSHost host, @@ -78,6 +87,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mController.observe(getLifecycle(), mCallback); + mExecutor = new HandlerExecutor(mainHandler); } @Override @@ -117,6 +127,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @Override + protected void handleSetListening(boolean listening) { + super.handleSetListening(listening); + + if (!listening) { + stopListeningToStaleDeviceMetadata(); + } + } + + @Override protected void handleUpdateState(BooleanState state, Object arg) { checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH); final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; @@ -125,6 +144,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { final boolean connecting = mController.isBluetoothConnecting(); state.isTransient = transientEnabling || connecting || mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON; + if (!enabled || !connected || state.isTransient) { + stopListeningToStaleDeviceMetadata(); + } state.dualTarget = true; state.value = enabled; if (state.slash == null) { @@ -187,23 +209,32 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices(); if (enabled && connected && !connectedDevices.isEmpty()) { if (connectedDevices.size() > 1) { + stopListeningToStaleDeviceMetadata(); return icuMessageFormat(mContext.getResources(), R.string.quick_settings_hotspot_secondary_label_num_devices, connectedDevices.size()); } - CachedBluetoothDevice lastDevice = connectedDevices.get(0); - final int batteryLevel = lastDevice.getBatteryLevel(); + CachedBluetoothDevice device = connectedDevices.get(0); + + // Use battery level provided by FastPair metadata if available. + // If not, fallback to the default battery level from bluetooth. + int batteryLevel = getMetadataBatteryLevel(device); + if (batteryLevel > BluetoothUtils.META_INT_ERROR) { + listenToMetadata(device); + } else { + stopListeningToStaleDeviceMetadata(); + batteryLevel = device.getBatteryLevel(); + } if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_battery_level, Utils.formatPercentage(batteryLevel)); - } else { - final BluetoothClass bluetoothClass = lastDevice.getBtClass(); + final BluetoothClass bluetoothClass = device.getBtClass(); if (bluetoothClass != null) { - if (lastDevice.isHearingAidDevice()) { + if (device.isHearingAidDevice()) { return mContext.getString( R.string.quick_settings_bluetooth_secondary_label_hearing_aids); } else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { @@ -233,6 +264,36 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return mController.isBluetoothSupported(); } + private int getMetadataBatteryLevel(CachedBluetoothDevice device) { + return BluetoothUtils.getIntMetaData(device.getDevice(), + BluetoothDevice.METADATA_MAIN_BATTERY); + } + + private void listenToMetadata(CachedBluetoothDevice cachedDevice) { + if (cachedDevice == mMetadataRegisteredDevice) return; + stopListeningToStaleDeviceMetadata(); + try { + mController.addOnMetadataChangedListener(cachedDevice, + mExecutor, + mMetadataChangedListener); + mMetadataRegisteredDevice = cachedDevice; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Battery metadata listener already registered for device."); + } + } + + private void stopListeningToStaleDeviceMetadata() { + if (mMetadataRegisteredDevice == null) return; + try { + mController.removeOnMetadataChangedListener( + mMetadataRegisteredDevice, + mMetadataChangedListener); + mMetadataRegisteredDevice = null; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Battery metadata listener already unregistered for device."); + } + } + private final BluetoothController.Callback mCallback = new BluetoothController.Callback() { @Override public void onBluetoothStateChange(boolean enabled) { @@ -244,4 +305,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { refreshState(); } }; + + private final BluetoothAdapter.OnMetadataChangedListener mMetadataChangedListener = + (device, key, value) -> { + if (key == BluetoothDevice.METADATA_MAIN_BATTERY) refreshState(); + }; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index aa2ea0b6cf3e..75d01723667d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -35,6 +35,7 @@ import android.widget.Switch; import androidx.annotation.Nullable; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.graph.SignalDrawable; @@ -174,6 +175,15 @@ public class InternetTile extends QSTileImpl<SignalState> { @Nullable String mEthernetContentDescription; + public void copyTo(EthernetCallbackInfo ethernetCallbackInfo) { + if (ethernetCallbackInfo == null) { + throw new IllegalArgumentException(); + } + ethernetCallbackInfo.mConnected = this.mConnected; + ethernetCallbackInfo.mEthernetSignalIconId = this.mEthernetSignalIconId; + ethernetCallbackInfo.mEthernetContentDescription = this.mEthernetContentDescription; + } + @Override public String toString() { return new StringBuilder("EthernetCallbackInfo[") @@ -200,6 +210,23 @@ public class InternetTile extends QSTileImpl<SignalState> { boolean mNoValidatedNetwork; boolean mNoNetworksAvailable; + public void copyTo(WifiCallbackInfo wifiCallbackInfo) { + if (wifiCallbackInfo == null) { + throw new IllegalArgumentException(); + } + wifiCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled; + wifiCallbackInfo.mEnabled = this.mEnabled; + wifiCallbackInfo.mConnected = this.mConnected; + wifiCallbackInfo.mWifiSignalIconId = this.mWifiSignalIconId; + wifiCallbackInfo.mSsid = this.mSsid; + wifiCallbackInfo.mWifiSignalContentDescription = this.mWifiSignalContentDescription; + wifiCallbackInfo.mIsTransient = this.mIsTransient; + wifiCallbackInfo.mStatusLabel = this.mStatusLabel; + wifiCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork; + wifiCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork; + wifiCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable; + } + @Override public String toString() { return new StringBuilder("WifiCallbackInfo[") @@ -232,6 +259,23 @@ public class InternetTile extends QSTileImpl<SignalState> { boolean mNoValidatedNetwork; boolean mNoNetworksAvailable; + public void copyTo(CellularCallbackInfo cellularCallbackInfo) { + if (cellularCallbackInfo == null) { + throw new IllegalArgumentException(); + } + cellularCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled; + cellularCallbackInfo.mDataSubscriptionName = this.mDataSubscriptionName; + cellularCallbackInfo.mDataContentDescription = this.mDataContentDescription; + cellularCallbackInfo.mMobileSignalIconId = this.mMobileSignalIconId; + cellularCallbackInfo.mQsTypeIcon = this.mQsTypeIcon; + cellularCallbackInfo.mNoSim = this.mNoSim; + cellularCallbackInfo.mRoaming = this.mRoaming; + cellularCallbackInfo.mMultipleSubs = this.mMultipleSubs; + cellularCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork; + cellularCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork; + cellularCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable; + } + @Override public String toString() { return new StringBuilder("CellularCallbackInfo[") @@ -251,8 +295,11 @@ public class InternetTile extends QSTileImpl<SignalState> { } protected final class InternetSignalCallback implements SignalCallback { + @GuardedBy("mWifiInfo") final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo(); + @GuardedBy("mCellularInfo") final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo(); + @GuardedBy("mEthernetInfo") final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo(); @@ -261,19 +308,23 @@ public class InternetTile extends QSTileImpl<SignalState> { if (DEBUG) { Log.d(TAG, "setWifiIndicators: " + indicators); } - mWifiInfo.mEnabled = indicators.enabled; - mWifiInfo.mSsid = indicators.description; - mWifiInfo.mIsTransient = indicators.isTransient; - mWifiInfo.mStatusLabel = indicators.statusLabel; + synchronized (mWifiInfo) { + mWifiInfo.mEnabled = indicators.enabled; + mWifiInfo.mSsid = indicators.description; + mWifiInfo.mIsTransient = indicators.isTransient; + mWifiInfo.mStatusLabel = indicators.statusLabel; + if (indicators.qsIcon != null) { + mWifiInfo.mConnected = indicators.qsIcon.visible; + mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon; + mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription; + } else { + mWifiInfo.mConnected = false; + mWifiInfo.mWifiSignalIconId = 0; + mWifiInfo.mWifiSignalContentDescription = null; + } + } if (indicators.qsIcon != null) { - mWifiInfo.mConnected = indicators.qsIcon.visible; - mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon; - mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription; refreshState(mWifiInfo); - } else { - mWifiInfo.mConnected = false; - mWifiInfo.mWifiSignalIconId = 0; - mWifiInfo.mWifiSignalContentDescription = null; } } @@ -286,14 +337,16 @@ public class InternetTile extends QSTileImpl<SignalState> { // Not data sim, don't display. return; } - mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null + synchronized (mCellularInfo) { + mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null ? mController.getMobileDataNetworkName() : indicators.qsDescription; - mCellularInfo.mDataContentDescription = indicators.qsDescription != null + mCellularInfo.mDataContentDescription = indicators.qsDescription != null ? indicators.typeContentDescriptionHtml : null; - mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon; - mCellularInfo.mQsTypeIcon = indicators.qsType; - mCellularInfo.mRoaming = indicators.roaming; - mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1; + mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon; + mCellularInfo.mQsTypeIcon = indicators.qsType; + mCellularInfo.mRoaming = indicators.roaming; + mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1; + } refreshState(mCellularInfo); } @@ -303,9 +356,11 @@ public class InternetTile extends QSTileImpl<SignalState> { Log.d(TAG, "setEthernetIndicators: " + "icon = " + (icon == null ? "" : icon.toString())); } - mEthernetInfo.mConnected = icon.visible; - mEthernetInfo.mEthernetSignalIconId = icon.icon; - mEthernetInfo.mEthernetContentDescription = icon.contentDescription; + synchronized (mEthernetInfo) { + mEthernetInfo.mConnected = icon.visible; + mEthernetInfo.mEthernetSignalIconId = icon.icon; + mEthernetInfo.mEthernetContentDescription = icon.contentDescription; + } if (icon.visible) { refreshState(mEthernetInfo); } @@ -318,11 +373,13 @@ public class InternetTile extends QSTileImpl<SignalState> { + "show = " + show + "," + "simDetected = " + simDetected); } - mCellularInfo.mNoSim = show; - if (mCellularInfo.mNoSim) { - // Make sure signal gets cleared out when no sims. - mCellularInfo.mMobileSignalIconId = 0; - mCellularInfo.mQsTypeIcon = 0; + synchronized (mCellularInfo) { + mCellularInfo.mNoSim = show; + if (mCellularInfo.mNoSim) { + // Make sure signal gets cleared out when no sims. + mCellularInfo.mMobileSignalIconId = 0; + mCellularInfo.mQsTypeIcon = 0; + } } } @@ -335,8 +392,12 @@ public class InternetTile extends QSTileImpl<SignalState> { if (mCellularInfo.mAirplaneModeEnabled == icon.visible) { return; } - mCellularInfo.mAirplaneModeEnabled = icon.visible; - mWifiInfo.mAirplaneModeEnabled = icon.visible; + synchronized (mCellularInfo) { + mCellularInfo.mAirplaneModeEnabled = icon.visible; + } + synchronized (mWifiInfo) { + mWifiInfo.mAirplaneModeEnabled = icon.visible; + } if (!mSignalCallback.mEthernetInfo.mConnected) { // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled, // because Internet Tile will show different information depending on whether WiFi @@ -363,12 +424,16 @@ public class InternetTile extends QSTileImpl<SignalState> { + "noValidatedNetwork = " + noValidatedNetwork + "," + "noNetworksAvailable = " + noNetworksAvailable); } - mCellularInfo.mNoDefaultNetwork = noDefaultNetwork; - mCellularInfo.mNoValidatedNetwork = noValidatedNetwork; - mCellularInfo.mNoNetworksAvailable = noNetworksAvailable; - mWifiInfo.mNoDefaultNetwork = noDefaultNetwork; - mWifiInfo.mNoValidatedNetwork = noValidatedNetwork; - mWifiInfo.mNoNetworksAvailable = noNetworksAvailable; + synchronized (mCellularInfo) { + mCellularInfo.mNoDefaultNetwork = noDefaultNetwork; + mCellularInfo.mNoValidatedNetwork = noValidatedNetwork; + mCellularInfo.mNoNetworksAvailable = noNetworksAvailable; + } + synchronized (mWifiInfo) { + mWifiInfo.mNoDefaultNetwork = noDefaultNetwork; + mWifiInfo.mNoValidatedNetwork = noValidatedNetwork; + mWifiInfo.mNoNetworksAvailable = noNetworksAvailable; + } if (!noDefaultNetwork) { return; } @@ -403,11 +468,23 @@ public class InternetTile extends QSTileImpl<SignalState> { // arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo // should be used to refresh the tile. if (mLastTileState == LAST_STATE_CELLULAR) { - handleUpdateCellularState(state, mSignalCallback.mCellularInfo); + CellularCallbackInfo cellularInfo = new CellularCallbackInfo(); + synchronized (mSignalCallback.mCellularInfo) { + mSignalCallback.mCellularInfo.copyTo(cellularInfo); + } + handleUpdateCellularState(state, cellularInfo); } else if (mLastTileState == LAST_STATE_WIFI) { - handleUpdateWifiState(state, mSignalCallback.mWifiInfo); + WifiCallbackInfo mifiInfo = new WifiCallbackInfo(); + synchronized (mSignalCallback.mWifiInfo) { + mSignalCallback.mWifiInfo.copyTo(mifiInfo); + } + handleUpdateWifiState(state, mifiInfo); } else if (mLastTileState == LAST_STATE_ETHERNET) { - handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo); + EthernetCallbackInfo ethernetInfo = new EthernetCallbackInfo(); + synchronized (mSignalCallback.mEthernetInfo) { + mSignalCallback.mEthernetInfo.copyTo(ethernetInfo); + } + handleUpdateEthernetState(state, ethernetInfo); } } } 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 a529da54fc4e..a38f52727c26 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 @@ -47,7 +47,6 @@ import android.util.FloatProperty; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; -import android.util.Property; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -336,8 +335,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView }; private boolean mKeepInParentForDismissAnimation; private boolean mRemoved; - private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT = - new FloatProperty<ExpandableNotificationRow>("translate") { + public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT = + new FloatProperty<>("translate") { @Override public void setValue(ExpandableNotificationRow object, float value) { object.setTranslation(value); @@ -348,6 +347,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return object.getTranslation(); } }; + private OnClickListener mOnClickListener; private OnDragSuccessListener mOnDragSuccessListener; private boolean mHeadsupDisappearRunning; @@ -2177,6 +2177,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return translateAnim; } + /** Cancels the ongoing translate animation if there is any. */ + public void cancelTranslateAnimation() { + if (mTranslateAnim != null) { + mTranslateAnim.cancel(); + } + } + void ensureGutsInflated() { if (mGuts == null) { mGutsStub.inflate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index c6f56d482d43..b476b683463f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -135,11 +135,15 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc @Override protected void onChildSnappedBack(View animView, float targetLeft) { + super.onChildSnappedBack(animView, targetLeft); + final NotificationMenuRowPlugin menuRow = getCurrentMenuRow(); if (menuRow != null && targetLeft == 0) { menuRow.resetMenu(); clearCurrentMenuRow(); } + + InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE); } @Override @@ -348,18 +352,13 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc super.dismissChild(view, velocity, useAccelerateInterpolator); } - @Override - protected void onSnapChildWithAnimationFinished() { - InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE); - } - @VisibleForTesting protected void superSnapChild(final View animView, final float targetLeft, float velocity) { super.snapChild(animView, targetLeft, velocity); } @Override - public void snapChild(final View animView, final float targetLeft, float velocity) { + protected void snapChild(final View animView, final float targetLeft, float velocity) { superSnapChild(animView, targetLeft, velocity); mCallback.onDragCancelled(animView); if (targetLeft == 0) { @@ -380,20 +379,18 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc } } + @Override @VisibleForTesting - protected Animator superGetViewTranslationAnimator(View v, float target, + protected Animator getViewTranslationAnimator(View view, float target, ValueAnimator.AnimatorUpdateListener listener) { - return super.getViewTranslationAnimator(v, target, listener); + return super.getViewTranslationAnimator(view, target, listener); } @Override - public Animator getViewTranslationAnimator(View v, float target, + @VisibleForTesting + protected Animator createTranslationAnimation(View view, float newPos, ValueAnimator.AnimatorUpdateListener listener) { - if (v instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); - } else { - return superGetViewTranslationAnimator(v, target, listener); - } + return super.createTranslationAnimation(view, newPos, listener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 2ee52325ca4a..654ba04eba7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -57,6 +57,8 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.concurrent.GuardedBy; + /** * Default implementation of a {@link BatteryController}. This controller monitors for battery * level change events that are broadcasted by the system. @@ -94,7 +96,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mTestMode = false; @VisibleForTesting boolean mHasReceivedBattery = false; + @GuardedBy("mEstimateLock") private Estimate mEstimate; + private final Object mEstimateLock = new Object(); + private boolean mFetchingEstimate = false; // Use AtomicReference because we may request it from a different thread @@ -321,7 +326,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Nullable private String generateTimeRemainingString() { - synchronized (mFetchCallbacks) { + synchronized (mEstimateLock) { if (mEstimate == null) { return null; } @@ -340,7 +345,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mFetchingEstimate = true; mBgHandler.post(() -> { // Only fetch the estimate if they are enabled - synchronized (mFetchCallbacks) { + synchronized (mEstimateLock) { mEstimate = null; if (mEstimates.isHybridNotificationEnabled()) { updateEstimate(); @@ -363,6 +368,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @WorkerThread + @GuardedBy("mEstimateLock") private void updateEstimate() { Assert.isNotMainThread(); // if the estimate has been cached we can just use that, otherwise get a new one and diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java index 0c5b8515071d..3429e25abfc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.policy; +import android.bluetooth.BluetoothAdapter; + import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.BluetoothController.Callback; import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; public interface BluetoothController extends CallbackController<Callback>, Dumpable { boolean isBluetoothSupported(); @@ -44,6 +47,11 @@ public interface BluetoothController extends CallbackController<Callback>, Dumpa int getBondState(CachedBluetoothDevice device); List<CachedBluetoothDevice> getConnectedDevices(); + void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener); + void removeOnMetadataChangedListener(CachedBluetoothDevice device, + BluetoothAdapter.OnMetadataChangedListener listener); + public interface Callback { void onBluetoothStateChange(boolean enabled); void onBluetoothDevicesChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index acdf0d2bc32b..c804fe76d882 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -48,6 +48,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.WeakHashMap; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -78,6 +79,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final H mHandler; private int mState; + private final BluetoothAdapter mAdapter; /** */ @Inject @@ -88,7 +90,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa BluetoothLogger logger, @Background Looper bgLooper, @Main Looper mainLooper, - @Nullable LocalBluetoothManager localBluetoothManager) { + @Nullable LocalBluetoothManager localBluetoothManager, + @Nullable BluetoothAdapter bluetoothAdapter) { mDumpManager = dumpManager; mLogger = logger; mLocalBluetoothManager = localBluetoothManager; @@ -103,6 +106,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mCurrentUser = userTracker.getUserId(); mDumpManager.registerDumpable(TAG, this); + mAdapter = bluetoothAdapter; } @Override @@ -412,6 +416,30 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } + public void addOnMetadataChangedListener( + @NonNull CachedBluetoothDevice cachedDevice, + Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener + ) { + if (mAdapter == null) return; + mAdapter.addOnMetadataChangedListener( + cachedDevice.getDevice(), + executor, + listener + ); + } + + public void removeOnMetadataChangedListener( + @NonNull CachedBluetoothDevice cachedDevice, + BluetoothAdapter.OnMetadataChangedListener listener + ) { + if (mAdapter == null) return; + mAdapter.removeOnMetadataChangedListener( + cachedDevice.getDevice(), + listener + ); + } + private ActuallyCachedState getCachedState(CachedBluetoothDevice device) { ActuallyCachedState state = mCachedState.get(device); if (state == null) { diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl new file mode 100644 index 000000000000..aa7ef57ea30c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl @@ -0,0 +1,7 @@ +package com.android.systemui.wallet.controller; + +import android.service.quickaccesswallet.WalletCard; + +interface IWalletCardsUpdatedListener { + void registerNewWalletCards(in List<WalletCard> cards); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl new file mode 100644 index 000000000000..eebbdfd06f7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl @@ -0,0 +1,9 @@ +package com.android.systemui.wallet.controller; + +import com.android.systemui.wallet.controller.IWalletCardsUpdatedListener; + +interface IWalletContextualLocationsService { + void addWalletCardsUpdatedListener(in IWalletCardsUpdatedListener listener); + + void onWalletContextualLocationsStateUpdated(in List<String> storeLocations); +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 2ef3511a0cce..ce2d15f97cd8 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -18,7 +18,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:sharedUserId="android.uid.system" - package="com.android.systemui" > + package="com.android.systemui.tests" > <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> @@ -64,7 +64,7 @@ </intent-filter> </receiver> - <activity android:name=".wmshell.BubblesTestActivity" + <activity android:name="com.android.systemui.wmshell.BubblesTestActivity" android:allowEmbedded="true" android:documentLaunchMode="always" android:excludeFromRecents="true" @@ -88,7 +88,7 @@ android:excludeFromRecents="true" /> - <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog" + <activity android:name="com.android.systemui.settings.brightness.BrightnessDialogTest$TestDialog" android:exported="false" android:excludeFromRecents="true" /> @@ -115,19 +115,19 @@ android:exported="false" /> <!-- started from UsbDeviceSettingsManager --> - <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable" + <activity android:name="com.android.systemui.usb.UsbPermissionActivityTest$UsbPermissionActivityTestable" android:exported="false" android:theme="@style/Theme.SystemUI.Dialog.Alert" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" /> - <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable" + <activity android:name="com.android.systemui.user.CreateUserActivityTest$CreateUserActivityTestable" android:exported="false" android:theme="@style/Theme.SystemUI.Dialog.Alert" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" /> - <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable" + <activity android:name="com.android.systemui.sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable" android:exported="false" android:theme="@style/Theme.SystemUI.Dialog.Alert" android:finishOnCloseSystemDialogs="true" diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt new file mode 100644 index 000000000000..6ddba0b4719c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shade.ShadeExpansionStateManager +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { + + private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + private lateinit var detector: AuthDialogPanelInteractionDetector + + @Mock private lateinit var action: Runnable + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Before + fun setUp() { + shadeExpansionStateManager = ShadeExpansionStateManager() + detector = + AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) + } + + @Test + fun testEnableDetector_shouldPostRunnable() { + detector.enable(action) + // simulate notification expand + shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f) + verify(action, timeout(5000).times(1)).run() + } + + @Test + fun testEnableDetector_shouldNotPostRunnable() { + var detector = + AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) + detector.enable(action) + detector.disable() + shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f) + verifyZeroInteractions(action) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index a636b7f43648..b87647ef9839 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -408,6 +408,17 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulsing_dozeSuspendTriggers_pulseDone_doesntCrash() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); + mMachine.requestState(DOZE_PULSING); + mMachine.requestState(DOZE_SUSPEND_TRIGGERS); + mMachine.requestState(DOZE_PULSE_DONE); + } + + @Test public void testSuppressingPulse_doesntCrash() { mMachine.requestState(INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index 5ec6283f3de0..bdc33f45c717 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -50,7 +51,6 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -90,9 +90,9 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, keyguardBypassController, ) - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) - `when`(repository.primaryBouncerShow.value).thenReturn(false) - `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.primaryBouncerShow.value).thenReturn(false) + whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate) resources = context.orCreateTestableResources } @@ -118,7 +118,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testShow_keyguardIsDone() { - `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true) + whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true) verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true) verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow() } @@ -135,7 +135,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion() { - `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) + whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) underTest.setPanelExpansion(0.6f) verify(repository).setPanelExpansion(0.6f) verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f) @@ -143,8 +143,8 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_fullyShown() { - `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) underTest.setPanelExpansion(EXPANSION_VISIBLE) verify(falsingCollector).onBouncerShown() verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown() @@ -152,8 +152,8 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_fullyHidden() { - `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) underTest.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setPrimaryShow(false) verify(falsingCollector).onBouncerHidden() @@ -163,7 +163,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_startingToHide() { - `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) + whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) underTest.setPanelExpansion(0.1f) verify(repository).setPrimaryStartingToHide(true) verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide() @@ -228,7 +228,21 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { } @Test - fun testStartDisappearAnimation() { + fun testStartDisappearAnimation_willRunDismissFromKeyguard() { + whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true) + + val runnable = mock(Runnable::class.java) + underTest.startDisappearAnimation(runnable) + // End runnable should run immediately + verify(runnable).run() + // ... while the disappear animation should never be run + verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java)) + } + + @Test + fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() { + whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false) + val runnable = mock(Runnable::class.java) underTest.startDisappearAnimation(runnable) verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java)) @@ -236,45 +250,45 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Test fun testIsFullShowing() { - `when`(repository.primaryBouncerShow.value).thenReturn(true) - `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.primaryBouncerShow.value).thenReturn(true) + whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) assertThat(underTest.isFullyShowing()).isTrue() - `when`(repository.primaryBouncerShow.value).thenReturn(false) + whenever(repository.primaryBouncerShow.value).thenReturn(false) assertThat(underTest.isFullyShowing()).isFalse() } @Test fun testIsScrimmed() { - `when`(repository.primaryBouncerScrimmed.value).thenReturn(true) + whenever(repository.primaryBouncerScrimmed.value).thenReturn(true) assertThat(underTest.isScrimmed()).isTrue() - `when`(repository.primaryBouncerScrimmed.value).thenReturn(false) + whenever(repository.primaryBouncerScrimmed.value).thenReturn(false) assertThat(underTest.isScrimmed()).isFalse() } @Test fun testIsInTransit() { - `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true) + whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true) assertThat(underTest.isInTransit()).isTrue() - `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false) + whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false) assertThat(underTest.isInTransit()).isFalse() - `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) + whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) assertThat(underTest.isInTransit()).isTrue() } @Test fun testIsAnimatingAway() { - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {}) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {}) assertThat(underTest.isAnimatingAway()).isTrue() - `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) + whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) assertThat(underTest.isAnimatingAway()).isFalse() } @Test fun testWillDismissWithAction() { - `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true) + whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true) assertThat(underTest.willDismissWithAction()).isTrue() - `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false) + whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false) assertThat(underTest.willDismissWithAction()).isFalse() } @@ -363,12 +377,13 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { isUnlockingWithFpAllowed: Boolean, isAnimatingAway: Boolean ) { - `when`(repository.primaryBouncerShow.value).thenReturn(isVisible) + whenever(repository.primaryBouncerShow.value).thenReturn(isVisible) resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled) - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning) - `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) + .thenReturn(fpsDetectionRunning) + whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) .thenReturn(isUnlockingWithFpAllowed) - `when`(repository.primaryBouncerStartingDisappearAnimation.value) + whenever(repository.primaryBouncerStartingDisappearAnimation.value) .thenReturn(if (isAnimatingAway) Runnable {} else null) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 746f66881a88..98794fd4de0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -115,7 +115,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { repository.sendTransitionStep(step(1f)) assertThat(values.size).isEqualTo(4) - values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) } + values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) } job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt index 464acb68fb07..01ffdcd580c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt @@ -19,12 +19,14 @@ package com.android.systemui.mediaprojection.appselector.view import android.content.Context import android.content.res.Configuration import android.content.res.Resources +import android.graphics.Insets import android.graphics.Rect import android.util.DisplayMetrics.DENSITY_DEFAULT +import android.view.WindowInsets import android.view.WindowManager import android.view.WindowMetrics +import androidx.core.view.WindowInsetsCompat.Type import androidx.test.filters.SmallTest -import com.android.internal.R import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener import com.android.systemui.statusbar.policy.FakeConfigurationController @@ -94,7 +96,13 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { } private fun givenTaskbarSize(size: Int) { - whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size) + val windowInsets = + WindowInsets.Builder() + .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size))) + .build() + val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets) + whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics) + whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics) } private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 75fd0000e0e1..2e77de270c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.qs.tiles -import android.content.Context +import android.bluetooth.BluetoothDevice import android.os.Handler import android.os.Looper import android.os.UserManager @@ -10,6 +10,8 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.settingslib.Utils +import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -21,14 +23,18 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BluetoothController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -36,21 +42,13 @@ import org.mockito.MockitoAnnotations @SmallTest class BluetoothTileTest : SysuiTestCase() { - @Mock - private lateinit var mockContext: Context - @Mock - private lateinit var qsLogger: QSLogger - @Mock - private lateinit var qsHost: QSHost - @Mock - private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var qsHost: QSHost + @Mock private lateinit var metricsLogger: MetricsLogger private val falsingManager = FalsingManagerFake() - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var bluetoothController: BluetoothController + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var bluetoothController: BluetoothController private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper @@ -61,20 +59,21 @@ class BluetoothTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - Mockito.`when`(qsHost.context).thenReturn(mockContext) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(qsHost.context).thenReturn(mContext) + whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) - tile = FakeBluetoothTile( - qsHost, - testableLooper.looper, - Handler(testableLooper.looper), - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - bluetoothController - ) + tile = + FakeBluetoothTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + bluetoothController, + ) tile.initialize() testableLooper.processAllMessages() @@ -102,7 +101,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -114,7 +113,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -126,7 +125,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) } @Test @@ -138,7 +137,76 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) + } + + @Test + fun testSecondaryLabel_whenBatteryMetadataAvailable_isMetadataBatteryLevelState() { + val cachedDevice = mock<CachedBluetoothDevice>() + val state = QSTile.BooleanState() + listenToDeviceMetadata(state, cachedDevice, 50) + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.secondaryLabel) + .isEqualTo( + mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_battery_level, + Utils.formatPercentage(50) + ) + ) + verify(bluetoothController) + .addOnMetadataChangedListener(eq(cachedDevice), any(), any()) + } + + @Test + fun testSecondaryLabel_whenBatteryMetadataUnavailable_isBluetoothBatteryLevelState() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + val cachedDevice2 = mock<CachedBluetoothDevice>() + val btDevice = mock<BluetoothDevice>() + whenever(cachedDevice2.device).thenReturn(btDevice) + whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null) + whenever(cachedDevice2.batteryLevel).thenReturn(25) + addConnectedDevice(cachedDevice2) + + tile.handleUpdateState(state, /* arg= */ null) + + assertThat(state.secondaryLabel) + .isEqualTo( + mContext.getString( + R.string.quick_settings_bluetooth_secondary_label_battery_level, + Utils.formatPercentage(25) + ) + ) + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) + } + + @Test + fun testMetadataListener_whenDisconnected_isUnregistered() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + disableBluetooth() + + tile.handleUpdateState(state, null) + + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) + } + + @Test + fun testMetadataListener_whenTileNotListening_isUnregistered() { + val state = QSTile.BooleanState() + val cachedDevice = mock<CachedBluetoothDevice>() + listenToDeviceMetadata(state, cachedDevice, 50) + + tile.handleSetListening(false) + + verify(bluetoothController, times(1)) + .removeOnMetadataChangedListener(eq(cachedDevice), any()) } private class FakeBluetoothTile( @@ -150,18 +218,19 @@ class BluetoothTileTest : SysuiTestCase() { statusBarStateController: StatusBarStateController, activityStarter: ActivityStarter, qsLogger: QSLogger, - bluetoothController: BluetoothController - ) : BluetoothTile( - qsHost, - backgroundLooper, - mainHandler, - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - bluetoothController - ) { + bluetoothController: BluetoothController, + ) : + BluetoothTile( + qsHost, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + bluetoothController, + ) { var restrictionChecked: String? = null override fun checkIfRestrictionEnforcedByAdminOnly( @@ -173,25 +242,44 @@ class BluetoothTileTest : SysuiTestCase() { } fun enableBluetooth() { - `when`(bluetoothController.isBluetoothEnabled).thenReturn(true) + whenever(bluetoothController.isBluetoothEnabled).thenReturn(true) } fun disableBluetooth() { - `when`(bluetoothController.isBluetoothEnabled).thenReturn(false) + whenever(bluetoothController.isBluetoothEnabled).thenReturn(false) } fun setBluetoothDisconnected() { - `when`(bluetoothController.isBluetoothConnecting).thenReturn(false) - `when`(bluetoothController.isBluetoothConnected).thenReturn(false) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) + whenever(bluetoothController.isBluetoothConnected).thenReturn(false) } fun setBluetoothConnected() { - `when`(bluetoothController.isBluetoothConnecting).thenReturn(false) - `when`(bluetoothController.isBluetoothConnected).thenReturn(true) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(false) + whenever(bluetoothController.isBluetoothConnected).thenReturn(true) } fun setBluetoothConnecting() { - `when`(bluetoothController.isBluetoothConnected).thenReturn(false) - `when`(bluetoothController.isBluetoothConnecting).thenReturn(true) + whenever(bluetoothController.isBluetoothConnected).thenReturn(false) + whenever(bluetoothController.isBluetoothConnecting).thenReturn(true) + } + + fun addConnectedDevice(device: CachedBluetoothDevice) { + whenever(bluetoothController.connectedDevices).thenReturn(listOf(device)) + } + + fun listenToDeviceMetadata( + state: QSTile.BooleanState, + cachedDevice: CachedBluetoothDevice, + batteryLevel: Int + ) { + val btDevice = mock<BluetoothDevice>() + whenever(cachedDevice.device).thenReturn(btDevice) + whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)) + .thenReturn(batteryLevel.toString().toByteArray()) + enableBluetooth() + setBluetoothConnected() + addConnectedDevice(cachedDevice) + tile.handleUpdateState(state, /* arg= */ null) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 78da78269ac4..824eb4aa25c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -425,12 +425,12 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { public void testGetViewTranslationAnimator_notExpandableNotificationRow() { Animator animator = mock(Animator.class); AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class); - doReturn(animator).when(mSwipeHelper).superGetViewTranslationAnimator(mView, 0, listener); + doReturn(animator).when(mSwipeHelper).createTranslationAnimation(mView, 0, listener); - assertEquals("returns the correct animator from super", animator, + assertEquals("Should create a new animator", animator, mSwipeHelper.getViewTranslationAnimator(mView, 0, listener)); - verify(mSwipeHelper, times(1)).superGetViewTranslationAnimator(mView, 0, listener); + verify(mSwipeHelper).createTranslationAnimation(mView, 0, listener); } @Test @@ -439,10 +439,10 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class); doReturn(animator).when(mNotificationRow).getTranslateViewAnimator(0, listener); - assertEquals("returns the correct animator from super when view is an ENR", animator, + assertEquals("Should return the animator from ExpandableNotificationRow", animator, mSwipeHelper.getViewTranslationAnimator(mNotificationRow, 0, listener)); - verify(mNotificationRow, times(1)).getTranslateViewAnimator(0, listener); + verify(mNotificationRow).getTranslateViewAnimator(0, listener); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 833cabbaecf4..7d64eaff0845 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +45,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -51,6 +54,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -60,10 +64,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private UserTracker mUserTracker; private LocalBluetoothManager mMockBluetoothManager; private CachedBluetoothDeviceManager mMockDeviceManager; - private LocalBluetoothAdapter mMockAdapter; + private LocalBluetoothAdapter mMockLocalAdapter; private TestableLooper mTestableLooper; private DumpManager mMockDumpManager; private BluetoothControllerImpl mBluetoothControllerImpl; + private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; @@ -74,10 +79,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mDevices = new ArrayList<>(); mUserTracker = mock(UserTracker.class); mMockDeviceManager = mock(CachedBluetoothDeviceManager.class); + mMockAdapter = mock(BluetoothAdapter.class); when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices); when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager); - mMockAdapter = mock(LocalBluetoothAdapter.class); - when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter); + mMockLocalAdapter = mock(LocalBluetoothAdapter.class); + when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockLocalAdapter); when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class)); when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); @@ -89,7 +95,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), - mMockBluetoothManager); + mMockBluetoothManager, + mMockAdapter); } @Test @@ -98,7 +105,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(device.isConnected()).thenReturn(true); when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED); mDevices.add(device); - when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED); + when(mMockLocalAdapter.getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_DISCONNECTED); mBluetoothControllerImpl.onConnectionStateChanged(null, BluetoothAdapter.STATE_DISCONNECTED); @@ -163,7 +171,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @Test public void testOnServiceConnected_updatesConnectionState() { - when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); + when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING); mBluetoothControllerImpl.onServiceConnected(); @@ -184,7 +192,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @Test public void testOnBluetoothStateChange_updatesConnectionState() { - when(mMockAdapter.getConnectionState()).thenReturn( + when(mMockLocalAdapter.getConnectionState()).thenReturn( BluetoothAdapter.STATE_CONNECTING, BluetoothAdapter.STATE_DISCONNECTED); @@ -240,6 +248,33 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); } + @Test + public void testAddOnMetadataChangedListener_registersListenerOnAdapter() { + CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + BluetoothDevice device = mock(BluetoothDevice.class); + when(cachedDevice.getDevice()).thenReturn(device); + Executor executor = new FakeExecutor(new FakeSystemClock()); + BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> { + }; + + mBluetoothControllerImpl.addOnMetadataChangedListener(cachedDevice, executor, listener); + + verify(mMockAdapter, times(1)).addOnMetadataChangedListener(device, executor, listener); + } + + @Test + public void testRemoveOnMetadataChangedListener_removesListenerFromAdapter() { + CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + BluetoothDevice device = mock(BluetoothDevice.class); + when(cachedDevice.getDevice()).thenReturn(device); + BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> { + }; + + mBluetoothControllerImpl.removeOnMetadataChangedListener(cachedDevice, listener); + + verify(mMockAdapter, times(1)).removeOnMetadataChangedListener(device, listener); + } + /** Regression test for b/246876230. */ @Test public void testOnActiveDeviceChanged_null_noCrash() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 8476d0d45603..bf54d4297ad8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -16,6 +16,9 @@ package com.android.systemui.unfold.updates +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources import android.os.Handler import android.testing.AndroidTestingRunner import androidx.core.util.Consumer @@ -33,6 +36,7 @@ import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenLis import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor import org.junit.Before @@ -49,20 +53,19 @@ import org.mockito.MockitoAnnotations @SmallTest class DeviceFoldStateProviderTest : SysuiTestCase() { - @Mock - private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider + @Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider - @Mock - private lateinit var handler: Handler + @Mock private lateinit var handler: Handler - @Mock - private lateinit var rotationChangeProvider: RotationChangeProvider + @Mock private lateinit var rotationChangeProvider: RotationChangeProvider - @Mock - private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider + @Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider - @Captor - private lateinit var rotationListener: ArgumentCaptor<RotationListener> + @Mock private lateinit var resources: Resources + + @Mock private lateinit var context: Context + + @Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener> private val foldProvider = TestFoldProvider() private val screenOnStatusProvider = TestScreenOnStatusProvider() @@ -81,10 +84,13 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() { - override val halfFoldedTimeoutMillis: Int - get() = HALF_OPENED_TIMEOUT_MILLIS.toInt() - } + val config = + object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() { + override val halfFoldedTimeoutMillis: Int + get() = HALF_OPENED_TIMEOUT_MILLIS.toInt() + } + whenever(context.resources).thenReturn(resources) + whenever(context.mainExecutor).thenReturn(mContext.mainExecutor) foldStateProvider = DeviceFoldStateProvider( @@ -95,6 +101,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { activityTypeProvider, unfoldKeyguardVisibilityProvider, rotationChangeProvider, + context, context.mainExecutor, handler ) @@ -112,7 +119,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { override fun onUnfoldedScreenAvailable() { unfoldedScreenAvailabilityUpdates.add(Unit) } - }) + } + ) foldStateProvider.start() verify(rotationChangeProvider).addCallback(capture(rotationListener)) @@ -134,6 +142,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { // By default, we're on launcher. setupForegroundActivityType(isHomeActivity = true) + setIsLargeScreen(true) } @Test @@ -181,7 +190,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(10) assertThat(foldUpdates) - .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) + .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1) } @@ -386,8 +395,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) sendHingeAngleEvent( - START_CLOSING_ON_APPS_THRESHOLD_DEGREES - - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1) + START_CLOSING_ON_APPS_THRESHOLD_DEGREES - + HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - + 1 + ) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } @@ -429,8 +440,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES) sendHingeAngleEvent( - START_CLOSING_ON_APPS_THRESHOLD_DEGREES - - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1) + START_CLOSING_ON_APPS_THRESHOLD_DEGREES - + HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - + 1 + ) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } @@ -470,7 +483,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(130) sendHingeAngleEvent(120) assertThat(foldUpdates) - .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) + .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING) } @Test @@ -531,8 +544,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { rotationListener.value.onRotationChanged(1) - assertThat(foldUpdates).containsExactly( - FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN) + assertThat(foldUpdates) + .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN) } @Test @@ -545,6 +558,45 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED) } + @Test + fun onFolding_onSmallScreen_tansitionDoesNotStart() { + setIsLargeScreen(false) + + setInitialHingeAngle(120) + sendHingeAngleEvent(110) + sendHingeAngleEvent(100) + + assertThat(foldUpdates).isEmpty() + } + + @Test + fun onFolding_onLargeScreen_tansitionStarts() { + setIsLargeScreen(true) + + setInitialHingeAngle(120) + sendHingeAngleEvent(110) + sendHingeAngleEvent(100) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) + } + + @Test + fun onUnfold_onSmallScreen_emitsStartOpening() { + // the new display state might arrive later, so it shouldn't be used to decide to send the + // start opening event, but only for the closing. + setFoldState(folded = true) + setIsLargeScreen(false) + foldUpdates.clear() + + setFoldState(folded = false) + screenOnStatusProvider.notifyScreenTurningOn() + sendHingeAngleEvent(10) + sendHingeAngleEvent(20) + screenOnStatusProvider.notifyScreenTurnedOn() + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING) + } + private fun setupForegroundActivityType(isHomeActivity: Boolean?) { whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity) } @@ -566,6 +618,13 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { foldProvider.notifyFolded(folded) } + private fun setIsLargeScreen(isLargeScreen: Boolean) { + val smallestScreenWidth = if (isLargeScreen) { 601 } else { 10 } + val configuration = Configuration() + configuration.smallestScreenWidthDp = smallestScreenWidth + whenever(resources.configuration).thenReturn(configuration) + } + private fun fireScreenOnEvent() { screenOnStatusProvider.notifyScreenTurnedOn() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 6cbd175c1084..4025ade5f715 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java @@ -14,6 +14,7 @@ package com.android.systemui.utils.leaks; +import android.bluetooth.BluetoothAdapter; import android.testing.LeakCheck; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -23,6 +24,7 @@ import com.android.systemui.statusbar.policy.BluetoothController.Callback; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; public class FakeBluetoothController extends BaseLeakChecker<Callback> implements BluetoothController { @@ -110,4 +112,16 @@ public class FakeBluetoothController extends BaseLeakChecker<Callback> implement public List<CachedBluetoothDevice> getConnectedDevices() { return Collections.emptyList(); } + + @Override + public void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor, + BluetoothAdapter.OnMetadataChangedListener listener) { + + } + + @Override + public void removeOnMetadataChangedListener(CachedBluetoothDevice device, + BluetoothAdapter.OnMetadataChangedListener listener) { + + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt index 2044f05664d0..380c1fcbf732 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt @@ -53,4 +53,4 @@ class ScreenSizeFoldProvider(private val context: Context) : FoldProvider { } } -private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 +internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index d653fc7beff2..a633a5e41882 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -15,12 +15,14 @@ */ package com.android.systemui.unfold.updates +import android.content.Context import android.os.Handler import android.os.Trace import android.util.Log import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting import androidx.core.util.Consumer +import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.dagger.UnfoldMain import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate @@ -45,6 +47,7 @@ constructor( private val activityTypeProvider: CurrentActivityTypeProvider, private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider, private val rotationChangeProvider: RotationChangeProvider, + private val context: Context, @UnfoldMain private val mainExecutor: Executor, @UnfoldMain private val handler: Handler ) : FoldStateProvider { @@ -119,7 +122,7 @@ constructor( "lastHingeAngle: $lastHingeAngle, " + "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition" ) - Trace.setCounter( "hinge_angle", angle.toLong()) + Trace.setCounter("hinge_angle", angle.toLong()) } val currentDirection = @@ -136,6 +139,7 @@ constructor( val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate val screenAvailableEventSent = isUnfoldHandled + val isOnLargeScreen = isOnLargeScreen() if ( angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle @@ -144,7 +148,9 @@ constructor( // angle range as closing threshold could overlap this range screenAvailableEventSent && // do not send transition event if we are still in the // process of turning on the inner display - isClosingThresholdMet(angle) // hinge angle is below certain threshold. + isClosingThresholdMet(angle) && // hinge angle is below certain threshold. + isOnLargeScreen // Avoids sending closing event when on small screen. + // Start event is sent regardless due to hall sensor. ) { notifyFoldUpdate(transitionUpdate, lastHingeAngle) } @@ -233,7 +239,7 @@ constructor( } private fun cancelAnimation(): Unit = - notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle) + notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle) private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener { @@ -261,6 +267,11 @@ constructor( } } + private fun isOnLargeScreen(): Boolean { + return context.resources.configuration.smallestScreenWidthDp > + INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + } + /** While the screen is off or the device is folded, hinge angle updates are not needed. */ private fun updateHingeAngleProviderState() { if (isScreenOn && !isFolded) { diff --git a/packages/WallpaperBackup/Android.bp b/packages/WallpaperBackup/Android.bp index d142f25c6a62..8acc5089f8bd 100644 --- a/packages/WallpaperBackup/Android.bp +++ b/packages/WallpaperBackup/Android.bp @@ -42,7 +42,7 @@ android_test { srcs: [ // Include the app source code because the app runs as the system user on-device. "src/**/*.java", - "test/src/**/*.java" + "test/src/**/*.java", ], libs: [ "android.test.base", @@ -54,7 +54,8 @@ android_test { "mockito-target-minus-junit4", "truth-prebuilt", ], + resource_dirs: ["test/res"], certificate: "platform", platform_apis: true, - test_suites: ["device-tests"] + test_suites: ["device-tests"], } diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index e549b61ac491..6aca2fdc0f7f 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -19,11 +19,18 @@ package com.android.wallpaperbackup; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED; + import android.app.AppGlobals; import android.app.WallpaperManager; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupManager; +import android.app.backup.BackupRestoreEventLogger.BackupRestoreError; import android.app.backup.FullBackupDataOutput; import android.content.ComponentName; import android.content.Context; @@ -103,6 +110,10 @@ public class WallpaperBackupAgent extends BackupAgent { private boolean mQuotaExceeded; private WallpaperManager mWallpaperManager; + private WallpaperEventLogger mEventLogger; + + private boolean mSystemHasLiveComponent; + private boolean mLockHasLiveComponent; @Override public void onCreate() { @@ -117,6 +128,9 @@ public class WallpaperBackupAgent extends BackupAgent { if (DEBUG) { Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded); } + + BackupManager backupManager = new BackupManager(getApplicationContext()); + mEventLogger = new WallpaperEventLogger(backupManager, /* wallpaperAgent */ this); } @Override @@ -149,11 +163,18 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged); } + // Due to the way image vs live wallpaper backup logic is intermingled, for logging + // purposes first check if we have live components for each wallpaper to avoid + // over-reporting errors. + mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null; + mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null; + backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); } catch (Exception e) { Slog.e(TAG, "Unable to back up wallpaper", e); + mEventLogger.onBackupException(e); } finally { // Even if this time we had to back off on attempting to store the lock image // due to exceeding the data quota, try again next time. This will alternate @@ -170,6 +191,14 @@ public class WallpaperBackupAgent extends BackupAgent { if (wallpaperInfoFd == null) { Slog.w(TAG, "Wallpaper metadata file doesn't exist"); + // If we have live components, getting the file to back up somehow failed, so log it + // as an error. + if (mSystemHasLiveComponent) { + mEventLogger.onSystemLiveWallpaperBackupFailed(ERROR_NO_METADATA); + } + if (mLockHasLiveComponent) { + mEventLogger.onLockLiveWallpaperBackupFailed(ERROR_NO_METADATA); + } return; } @@ -182,12 +211,22 @@ public class WallpaperBackupAgent extends BackupAgent { if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata"); backupFile(infoStage, data); + + // We've backed up the info file which contains the live component, so log it as success + if (mSystemHasLiveComponent) { + mEventLogger.onSystemLiveWallpaperBackedUp( + mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM)); + } + if (mLockHasLiveComponent) { + mEventLogger.onLockLiveWallpaperBackedUp(mWallpaperManager.getWallpaperInfo(FLAG_LOCK)); + } } private void backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException { if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) { Slog.d(TAG, "System wallpaper ineligible for backup"); + logSystemImageErrorIfNoLiveComponent(ERROR_INELIGIBLE); return; } @@ -197,6 +236,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (systemWallpaperImageFd == null) { Slog.w(TAG, "System wallpaper doesn't exist"); + logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); return; } @@ -210,8 +250,17 @@ public class WallpaperBackupAgent extends BackupAgent { if (DEBUG) Slog.v(TAG, "Storing system wallpaper image"); backupFile(imageStage, data); sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); + mEventLogger.onSystemImageWallpaperBackedUp(); } + private void logSystemImageErrorIfNoLiveComponent(@BackupRestoreError String error) { + if (mSystemHasLiveComponent) { + return; + } + mEventLogger.onSystemImageWallpaperBackupFailed(error); + } + + private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); @@ -224,11 +273,13 @@ public class WallpaperBackupAgent extends BackupAgent { } Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup"); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); + logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); return; } if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) { Slog.d(TAG, "Lock screen wallpaper ineligible for backup"); + logLockImageErrorIfNoLiveComponent(ERROR_INELIGIBLE); return; } @@ -239,11 +290,13 @@ public class WallpaperBackupAgent extends BackupAgent { // set, but we can't find it. if (lockWallpaperFd == null) { Slog.w(TAG, "Lock wallpaper doesn't exist"); + logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); return; } if (mQuotaExceeded) { Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time"); + logLockImageErrorIfNoLiveComponent(ERROR_QUOTA_EXCEEDED); return; } @@ -255,6 +308,14 @@ public class WallpaperBackupAgent extends BackupAgent { if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image"); backupFile(lockImageStage, data); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); + mEventLogger.onLockImageWallpaperBackedUp(); + } + + private void logLockImageErrorIfNoLiveComponent(@BackupRestoreError String error) { + if (mLockHasLiveComponent) { + return; + } + mEventLogger.onLockImageWallpaperBackupFailed(error); } /** diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java new file mode 100644 index 000000000000..64944b3ff54f --- /dev/null +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java @@ -0,0 +1,139 @@ +/* + * 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.wallpaperbackup; + +import android.annotation.Nullable; +import android.app.WallpaperInfo; +import android.app.backup.BackupManager; +import android.app.backup.BackupRestoreEventLogger; +import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType; +import android.app.backup.BackupRestoreEventLogger.BackupRestoreError; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashSet; +import java.util.Set; + +/** + * Log backup / restore related events using {@link BackupRestoreEventLogger}. + */ +public class WallpaperEventLogger { + /* Static image used as system (or home) screen wallpaper */ + @BackupRestoreDataType + @VisibleForTesting + static final String WALLPAPER_IMG_SYSTEM = "wlp_img_system"; + + /* Static image used as lock screen wallpaper */ + @BackupRestoreDataType + @VisibleForTesting + static final String WALLPAPER_IMG_LOCK = "wlp_img_lock"; + + /* Live component used as system (or home) screen wallpaper */ + @BackupRestoreDataType + @VisibleForTesting + static final String WALLPAPER_LIVE_SYSTEM = "wlp_live_system"; + + /* Live component used as lock screen wallpaper */ + @BackupRestoreDataType + @VisibleForTesting + static final String WALLPAPER_LIVE_LOCK = "wlp_live_lock"; + + @BackupRestoreError + static final String ERROR_INELIGIBLE = "ineligible"; + @BackupRestoreError + static final String ERROR_NO_METADATA = "no_metadata"; + @BackupRestoreError + static final String ERROR_NO_WALLPAPER = "no_wallpaper"; + @BackupRestoreError + static final String ERROR_QUOTA_EXCEEDED = "quota_exceeded"; + + private final BackupRestoreEventLogger mLogger; + + private final Set<String> mProcessedDataTypes = new HashSet<>(); + + WallpaperEventLogger(BackupManager backupManager, WallpaperBackupAgent wallpaperAgent) { + mLogger = backupManager.getBackupRestoreEventLogger(/* backupAgent */ wallpaperAgent); + } + + void onSystemImageWallpaperBackedUp() { + logBackupSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null); + } + + void onLockImageWallpaperBackedUp() { + logBackupSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null); + } + + void onSystemLiveWallpaperBackedUp(WallpaperInfo wallpaperInfo) { + logBackupSuccessInternal(WALLPAPER_LIVE_SYSTEM, wallpaperInfo); + } + + void onLockLiveWallpaperBackedUp(WallpaperInfo wallpaperInfo) { + logBackupSuccessInternal(WALLPAPER_LIVE_LOCK, wallpaperInfo); + } + + void onSystemImageWallpaperBackupFailed(@BackupRestoreError String error) { + logBackupFailureInternal(WALLPAPER_IMG_SYSTEM, error); + } + + void onLockImageWallpaperBackupFailed(@BackupRestoreError String error) { + logBackupFailureInternal(WALLPAPER_IMG_LOCK, error); + } + + void onSystemLiveWallpaperBackupFailed(@BackupRestoreError String error) { + logBackupFailureInternal(WALLPAPER_LIVE_SYSTEM, error); + } + + void onLockLiveWallpaperBackupFailed(@BackupRestoreError String error) { + logBackupFailureInternal(WALLPAPER_LIVE_LOCK, error); + } + + + /** + * Called when the whole backup flow is interrupted by an exception. + */ + void onBackupException(Exception exception) { + String error = exception.getClass().getName(); + if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains( + WALLPAPER_LIVE_SYSTEM)) { + mLogger.logItemsBackupFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error); + } + if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains( + WALLPAPER_LIVE_LOCK)) { + mLogger.logItemsBackupFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error); + } + } + + private void logBackupSuccessInternal(@BackupRestoreDataType String which, + @Nullable WallpaperInfo liveComponentWallpaperInfo) { + mLogger.logItemsBackedUp(which, /* count */ 1); + logLiveWallpaperNameIfPresent(which, liveComponentWallpaperInfo); + mProcessedDataTypes.add(which); + } + + private void logBackupFailureInternal(@BackupRestoreDataType String which, + @BackupRestoreError String error) { + mLogger.logItemsBackupFailed(which, /* count */ 1, error); + mProcessedDataTypes.add(which); + } + + private void logLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType, + WallpaperInfo wallpaperInfo) { + if (wallpaperInfo != null) { + mLogger.logBackupMetadata(wallpaperType, wallpaperInfo.getComponent().getClassName()); + } + } +} diff --git a/packages/WallpaperBackup/test/AndroidManifest.xml b/packages/WallpaperBackup/test/AndroidManifest.xml index 44ab1b6d65ba..eb1e98b90808 100644 --- a/packages/WallpaperBackup/test/AndroidManifest.xml +++ b/packages/WallpaperBackup/test/AndroidManifest.xml @@ -4,6 +4,21 @@ <application android:label="WallpaperBackup Tests"> <uses-library android:name="android.test.runner" /> + <service android:name="com.android.wallpaperbackup.utils.TestWallpaperService" + android:enabled="true" + android:directBootAware="true" + android:label="Test wallpaper" + android:permission="android.permission.BIND_WALLPAPER" + android:exported="true"> + + <intent-filter> + <action android:name="android.service.wallpaper.WallpaperService"/> + </intent-filter> + + <!-- Link to XML that defines the wallpaper info. --> + <meta-data android:name="android.service.wallpaper" + android:resource="@xml/livewallpaper"/> + </service> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/packages/WallpaperBackup/test/res/xml/livewallpaper.xml b/packages/WallpaperBackup/test/res/xml/livewallpaper.xml new file mode 100644 index 000000000000..c6fbe2bda908 --- /dev/null +++ b/packages/WallpaperBackup/test/res/xml/livewallpaper.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<wallpaper/> diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 20dd516503b8..89459f6e6772 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -23,22 +23,40 @@ import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.android.wallpaperbackup.WallpaperBackupAgent.LOCK_WALLPAPER_STAGE; import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE; import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.WallpaperInfo; import android.app.WallpaperManager; +import android.app.backup.BackupAnnotations; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.FullBackupDataOutput; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.FileUtils; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.service.wallpaper.WallpaperService; +import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; @@ -69,12 +87,18 @@ public class WallpaperBackupAgentTest { private static final int TEST_SYSTEM_WALLPAPER_ID = 1; private static final int TEST_LOCK_WALLPAPER_ID = 2; private static final int NO_LOCK_WALLPAPER_ID = -1; + // An arbitrary user. + private static final UserHandle USER_HANDLE = new UserHandle(15); - @Mock private FullBackupDataOutput mOutput; - @Mock private WallpaperManager mWallpaperManager; - @Mock private Context mMockContext; + @Mock + private FullBackupDataOutput mOutput; + @Mock + private WallpaperManager mWallpaperManager; + @Mock + private Context mMockContext; - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + @Rule + public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); private ContextWithServiceOverrides mContext; private IsolatedWallpaperBackupAgent mWallpaperBackupAgent; @@ -90,9 +114,10 @@ public class WallpaperBackupAgentTest { mContext = new ContextWithServiceOverrides(ApplicationProvider.getApplicationContext()); mContext.injectSystemService(WallpaperManager.class, mWallpaperManager); - mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot()); + mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(); mWallpaperBackupAgent.attach(mContext); - mWallpaperBackupAgent.onCreate(); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.BACKUP); mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, ""); } @@ -388,6 +413,185 @@ public class WallpaperBackupAgentTest { verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK)); } + @Test + public void testOnFullBackup_systemWallpaperImgSuccess_logsSuccess() throws Exception { + mockSystemWallpaperFileWithContents("system wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void testOnFullBackup_systemWallpaperImgIneligible_logsFailure() throws Exception { + when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(false); + mockSystemWallpaperFileWithContents("system wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_INELIGIBLE); + } + + @Test + public void testOnFullBackup_systemWallpaperImgMissing_logsFailure() throws Exception { + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER); + } + + @Test + public void testOnFullBackup_systemWallpaperImgMissingButHasLiveComponent_logsLiveSuccess() + throws Exception { + mockWallpaperInfoFileWithContents("info file"); + when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo()); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_LIVE_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + assertThat(result.getMetadataHash()).isNotNull(); + } + + @Test + public void testOnFullBackup_systemWallpaperImgMissingButHasLiveComponent_logsNothingForImg() + throws Exception { + mockWallpaperInfoFileWithContents("info file"); + when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo()); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNull(); + } + + @Test + public void testOnFullBackup_lockWallpaperImgSuccess_logsSuccess() throws Exception { + mockLockWallpaperFileWithContents("lock wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void testOnFullBackup_lockWallpaperImgIneligible_logsFailure() throws Exception { + when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(false); + mockLockWallpaperFileWithContents("lock wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_INELIGIBLE); + } + + @Test + public void testOnFullBackup_lockWallpaperImgMissing_logsFailure() throws Exception { + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER); + } + + @Test + public void testOnFullBackup_lockWallpaperImgMissingButHasLiveComponent_logsLiveSuccess() + throws Exception { + mockWallpaperInfoFileWithContents("info file"); + when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo()); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_LIVE_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + assertThat(result.getMetadataHash()).isNotNull(); + } + + @Test + public void testOnFullBackup_lockWallpaperImgMissingButHasLiveComponent_logsNothingForImg() + throws Exception { + mockWallpaperInfoFileWithContents("info file"); + when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo()); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNull(); + } + + + @Test + public void testOnFullBackup_exceptionThrown_logsException() throws Exception { + when(mWallpaperManager.isWallpaperBackupEligible(anyInt())).thenThrow( + new RuntimeException()); + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(RuntimeException.class.getName()); + } + + @Test + public void testOnFullBackup_lastBackupOverQuota_logsLockFailure() throws Exception { + mockSystemWallpaperFileWithContents("system wallpaper"); + mockLockWallpaperFileWithContents("lock wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); + markAgentAsOverQuota(); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_QUOTA_EXCEEDED); + } + + @Test + public void testOnFullBackup_lastBackupOverQuota_logsSystemSuccess() throws Exception { + mockSystemWallpaperFileWithContents("system wallpaper"); + mockLockWallpaperFileWithContents("lock wallpaper"); + mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID); + markAgentAsOverQuota(); + + mWallpaperBackupAgent.onFullBackup(mOutput); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) { when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId); when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId); @@ -432,16 +636,41 @@ public class WallpaperBackupAgentTest { ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY)); } + private WallpaperInfo getFakeWallpaperInfo() throws Exception { + Context context = InstrumentationRegistry.getTargetContext(); + Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + intent.setPackage("com.android.wallpaperbackup.tests"); + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA); + assertEquals(1, result.size()); + ResolveInfo info = result.get(0); + return new WallpaperInfo(context, info); + } + + private void markAgentAsOverQuota() throws Exception { + // Create over quota file to indicate the last backup was over quota + File quotaFile = new File(mContext.getFilesDir(), WallpaperBackupAgent.QUOTA_SENTINEL); + quotaFile.createNewFile(); + + // Now redo the setup of the agent to pick up the over quota + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.BACKUP); + } + + private static DataTypeResult getLoggingResult(String dataType, List<DataTypeResult> results) { + for (DataTypeResult result : results) { + if ((result.getDataType()).equals(dataType)) { + return result; + } + } + return null; + } + private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent { - File mWallpaperBaseDirectory; List<File> mBackedUpFiles = new ArrayList<>(); PackageMonitor mWallpaperPackageMonitor; boolean mIsDeviceInRestore = false; - IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) { - mWallpaperBaseDirectory = wallpaperBaseDirectory; - } - @Override protected void backupFile(File file, FullBackupDataOutput data) { mBackedUpFiles.add(file); diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java new file mode 100644 index 000000000000..3816a3ccc1eb --- /dev/null +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java @@ -0,0 +1,204 @@ +/* + * 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.wallpaperbackup; + +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK; +import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.WallpaperInfo; +import android.app.backup.BackupManager; +import android.app.backup.BackupRestoreEventLogger; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.service.wallpaper.WallpaperService; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wallpaperbackup.utils.TestWallpaperService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class WallpaperEventLoggerTest { + + @Mock + private BackupRestoreEventLogger mMockLogger; + + @Mock + private BackupManager mMockBackupManager; + + @Mock + private WallpaperBackupAgent mMockBackupAgent; + + private static final String WALLPAPER_ERROR = "some_error"; + + private WallpaperEventLogger mWallpaperEventLogger; + private WallpaperInfo mWallpaperInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mMockLogger); + when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mMockLogger); + + mWallpaperInfo = getWallpaperInfo(); + mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent); + } + + @Test + public void onSystemImgWallpaperBackedUp_logsSuccess() { + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + + verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_SYSTEM), eq(1)); + } + + @Test + public void onLockImgWallpaperBackedUp_logsSuccess() { + mWallpaperEventLogger.onLockImageWallpaperBackedUp(); + + verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_LOCK), eq(1)); + } + + @Test + public void onSystemLiveWallpaperBackedUp_logsSuccess() { + mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); + + verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_SYSTEM), eq(1)); + } + + @Test + public void onLockLiveWallpaperBackedUp_logsSuccess() { + mWallpaperEventLogger.onLockLiveWallpaperBackedUp(mWallpaperInfo); + + verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_LOCK), eq(1)); + } + + @Test + public void onImgWallpaperBackedUp_nullInfo_doesNotLogMetadata() { + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + + verify(mMockLogger, never()).logBackupMetadata(eq(WALLPAPER_IMG_SYSTEM), anyString()); + } + + + @Test + public void onLiveWallpaperBackedUp_logsMetadata() { + mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); + + verify(mMockLogger).logBackupMetadata(eq(WALLPAPER_LIVE_SYSTEM), + eq(TestWallpaperService.class.getName())); + } + + + @Test + public void onSystemImgWallpaperBackupFailed_logsFail() { + mWallpaperEventLogger.onSystemImageWallpaperBackupFailed(WALLPAPER_ERROR); + + verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), eq(1), + eq(WALLPAPER_ERROR)); + } + + @Test + public void onLockImgWallpaperBackupFailed_logsFail() { + mWallpaperEventLogger.onLockImageWallpaperBackupFailed(WALLPAPER_ERROR); + + verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1), + eq(WALLPAPER_ERROR)); + } + + + @Test + public void onSystemLiveWallpaperBackupFailed_logsFail() { + mWallpaperEventLogger.onSystemLiveWallpaperBackupFailed(WALLPAPER_ERROR); + + verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_SYSTEM), eq(1), + eq(WALLPAPER_ERROR)); + } + + @Test + public void onLockLiveWallpaperBackupFailed_logsFail() { + mWallpaperEventLogger.onLockLiveWallpaperBackupFailed(WALLPAPER_ERROR); + + verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_LOCK), eq(1), + eq(WALLPAPER_ERROR)); + } + + + @Test + public void onWallpaperBackupException_someProcessed_doesNotLogErrorForProcessedType() { + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + + mWallpaperEventLogger.onBackupException(new Exception()); + + verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(), + anyString()); + } + + + @Test + public void onWallpaperBackupException_someProcessed_logsErrorForUnprocessedType() { + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + + mWallpaperEventLogger.onBackupException(new Exception()); + + verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1), + eq(Exception.class.getName())); + + } + + @Test + public void onWallpaperBackupException_liveTypeProcessed_doesNotLogErrorForSameImgType() { + mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); + + mWallpaperEventLogger.onBackupException(new Exception()); + + verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(), + anyString()); + } + + private WallpaperInfo getWallpaperInfo() throws Exception { + Context context = InstrumentationRegistry.getTargetContext(); + Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + intent.setPackage("com.android.wallpaperbackup.tests"); + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA); + assertEquals(1, result.size()); + ResolveInfo info = result.get(0); + return new WallpaperInfo(context, info); + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java index 8fb4e91f790c..cb8504132e45 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -13,9 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests.b; -import android.content.componentalias.tests.s.BaseService; +package com.android.wallpaperbackup.utils; -public class Target00 extends BaseReceiver { +import android.service.wallpaper.WallpaperService; + +/** + * Empty wallpaper service used for wallpaper backup tests + */ +public class TestWallpaperService extends WallpaperService { + @Override + public Engine onCreateEngine() { + return new Engine(); + } } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 7d1de40c7150..ca743cbb1867 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -26,6 +26,12 @@ import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_CLICKED; import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_COMMITTED; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU; @@ -84,6 +90,32 @@ public final class PresentationStatsEventLogger { @Retention(RetentionPolicy.SOURCE) public @interface NotShownReason {} + /** + * Reasons why presentation was not shown. These are wrappers around + * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationType}. + */ + @IntDef(prefix = {"AUTHENTICATION_TYPE"}, value = { + AUTHENTICATION_TYPE_UNKNOWN, + AUTHENTICATION_TYPE_DATASET_AUTHENTICATION, + AUTHENTICATION_TYPE_FULL_AUTHENTICATION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AuthenticationType { + } + + /** + * Reasons why presentation was not shown. These are wrappers around + * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationResult}. + */ + @IntDef(prefix = {"AUTHENTICATION_RESULT"}, value = { + AUTHENTICATION_RESULT_UNKNOWN, + AUTHENTICATION_RESULT_SUCCESS, + AUTHENTICATION_RESULT_FAILURE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AuthenticationResult { + } + public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = @@ -105,6 +137,20 @@ public final class PresentationStatsEventLogger { public static final int NOT_SHOWN_REASON_UNKNOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; + public static final int AUTHENTICATION_TYPE_UNKNOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN; + public static final int AUTHENTICATION_TYPE_DATASET_AUTHENTICATION = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION; + public static final int AUTHENTICATION_TYPE_FULL_AUTHENTICATION = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION; + + public static final int AUTHENTICATION_RESULT_UNKNOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN; + public static final int AUTHENTICATION_RESULT_SUCCESS = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS; + public static final int AUTHENTICATION_RESULT_FAILURE = + AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE; + private final int mSessionId; private Optional<PresentationStatsEventInternal> mEventInternal; @@ -146,7 +192,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId); event.mAvailableCount = availableCount; @@ -155,7 +201,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetCountShown(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int countShown = getDatasetCountForAutofillId(datasetList, currentViewId); event.mCountShown = countShown; @@ -166,7 +212,7 @@ public final class PresentationStatsEventLogger { } private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { int availableCount = 0; if (datasetList != null) { for (int i = 0; i < datasetList.size(); i++) { @@ -293,6 +339,43 @@ public final class PresentationStatsEventLogger { }); } + /** + * Set authentication_type as long as mEventInternal presents. + */ + public void maybeSetAuthenticationType(@AuthenticationType int val) { + mEventInternal.ifPresent(event -> { + event.mAuthenticationType = val; + }); + } + + /** + * Set authentication_result as long as mEventInternal presents. + */ + public void maybeSetAuthenticationResult(@AuthenticationResult int val) { + mEventInternal.ifPresent(event -> { + event.mAuthenticationResult = val; + }); + } + + /** + * Set latency_authentication_ui_display_millis as long as mEventInternal presents. + */ + public void maybeSetLatencyAuthenticationUiDisplayMillis(int val) { + mEventInternal.ifPresent(event -> { + event.mLatencyAuthenticationUiDisplayMillis = val; + }); + } + + /** + * Set latency_dataset_display_millis as long as mEventInternal presents. + */ + public void maybeSetLatencyDatasetDisplayMillis(int val) { + mEventInternal.ifPresent(event -> { + event.mLatencyDatasetDisplayMillis = val; + }); + } + + public void logAndEndEvent() { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " @@ -322,7 +405,12 @@ public final class PresentationStatsEventLogger { + " mSelectedDatasetId=" + event.mSelectedDatasetId + " mDialogDismissed=" + event.mDialogDismissed + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked - + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked); + + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked + + " mAuthenticationType=" + event.mAuthenticationType + + " mAuthenticationResult=" + event.mAuthenticationResult + + " mLatencyAuthenticationUiDisplayMillis=" + + event.mLatencyAuthenticationUiDisplayMillis + + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -351,11 +439,15 @@ public final class PresentationStatsEventLogger { event.mSelectedDatasetId, event.mDialogDismissed, event.mNegativeCtaButtonClicked, - event.mPositiveCtaButtonClicked); + event.mPositiveCtaButtonClicked, + event.mAuthenticationType, + event.mAuthenticationResult, + event.mLatencyAuthenticationUiDisplayMillis, + event.mLatencyDatasetDisplayMillis); mEventInternal = Optional.empty(); } - private final class PresentationStatsEventInternal { + private static final class PresentationStatsEventInternal { int mRequestId; @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN; boolean mIsDatasetAvailable; @@ -376,6 +468,10 @@ public final class PresentationStatsEventLogger { boolean mDialogDismissed = false; boolean mNegativeCtaButtonClicked = false; boolean mPositiveCtaButtonClicked = false; + int mAuthenticationType = AUTHENTICATION_TYPE_UNKNOWN; + int mAuthenticationResult = AUTHENTICATION_RESULT_UNKNOWN; + int mLatencyAuthenticationUiDisplayMillis = -1; + int mLatencyDatasetDisplayMillis = -1; PresentationStatsEventInternal() {} } diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java index 01735a754c83..f9eaf0229b85 100644 --- a/services/core/java/com/android/server/am/ComponentAliasResolver.java +++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java @@ -30,7 +30,6 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Binder; -import android.os.Build; import android.os.ServiceManager; import android.os.UserHandle; import android.text.TextUtils; @@ -43,7 +42,6 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.server.FgThread; import com.android.server.LocalServices; -import com.android.server.compat.CompatChange; import com.android.server.compat.PlatformCompat; import java.io.PrintWriter; @@ -52,26 +50,11 @@ import java.util.Objects; import java.util.function.Supplier; /** - * Manages and handles component aliases, which is an experimental feature. + * @deprecated This feature is no longer used. Delete this class. * - * NOTE: THIS CLASS IS PURELY EXPERIMENTAL AND WILL BE REMOVED IN FUTURE ANDROID VERSIONS. - * DO NOT USE IT. - * - * "Component alias" allows an android manifest component (for now only broadcasts and services) - * to be defined in one android package while having the implementation in a different package. - * - * When/if this becomes a real feature, it will be most likely implemented very differently, - * which is why this shouldn't be used. - * - * For now, because this is an experimental feature to evaluate feasibility, the implementation is - * "quick & dirty". For example, to define aliases, we use a regular intent filter and meta-data - * in the manifest, instead of adding proper tags/attributes to AndroidManifest.xml. - * - * This feature is disabled by default. - * - * Also, for now, aliases can be defined across packages with different certificates, but - * in a final version this will most likely be tightened. + * Also delete Intnt.(set|get)OriginalIntent. */ +@Deprecated public class ComponentAliasResolver { private static final String TAG = "ComponentAliasResolver"; private static final boolean DEBUG = true; @@ -149,11 +132,6 @@ public class ComponentAliasResolver { } }; - private final CompatChange.ChangeListener mCompatChangeListener = (packageName) -> { - if (DEBUG) Slog.d(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS changed."); - BackgroundThread.getHandler().post(this::refresh); - }; - /** * Call this on systemRead(). */ @@ -161,8 +139,6 @@ public class ComponentAliasResolver { synchronized (mLock) { mPlatformCompat = (PlatformCompat) ServiceManager.getService( Context.PLATFORM_COMPAT_SERVICE); - mPlatformCompat.registerListener(USE_EXPERIMENTAL_COMPONENT_ALIAS, - mCompatChangeListener); } if (DEBUG) Slog.d(TAG, "Compat listener set."); update(enabledByDeviceConfig, overrides); @@ -176,10 +152,8 @@ public class ComponentAliasResolver { if (mPlatformCompat == null) { return; // System not ready. } - final boolean enabled = Build.isDebuggable() - && (enabledByDeviceConfig - || mPlatformCompat.isChangeEnabledByPackageName( - USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM)); + // Never enable it. + final boolean enabled = false; if (enabled != mEnabled) { Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases..."); FgThread.getHandler().post(() -> { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4e687f4929cf..16bf355f0634 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -170,6 +170,7 @@ import android.provider.Settings; import android.provider.Settings.System; import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; +import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; @@ -184,6 +185,7 @@ import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -393,6 +395,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_NO_LOG_FOR_PLAYER_I = 51; private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52; private static final int MSG_LOWER_VOLUME_TO_RS1 = 53; + private static final int MSG_CONFIGURATION_CHANGED = 54; /** Messages handled by the {@link SoundDoseHelper}. */ /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000; @@ -1219,17 +1222,6 @@ public class AudioService extends IAudioService.Stub updateAudioHalPids(); - boolean cameraSoundForced = readCameraSoundForced(); - mCameraSoundForced = new Boolean(cameraSoundForced); - sendMsg(mAudioHandler, - MSG_SET_FORCE_USE, - SENDMSG_QUEUE, - AudioSystem.FOR_SYSTEM, - cameraSoundForced ? - AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, - new String("AudioService ctor"), - 0); - mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); @@ -1314,6 +1306,18 @@ public class AudioService extends IAudioService.Stub * Called by handling of MSG_INIT_STREAMS_VOLUMES */ private void onInitStreamsAndVolumes() { + synchronized (mSettingsLock) { + mCameraSoundForced = readCameraSoundForced(); + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_SYSTEM, + mCameraSoundForced + ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, + new String("AudioService ctor"), + 0); + } + createStreamStates(); // must be called after createStreamStates() as it uses MUSIC volume as default if no @@ -1349,8 +1353,19 @@ public class AudioService extends IAudioService.Stub // check on volume initialization checkVolumeRangeInitialization("AudioService()"); + } + private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = + new SubscriptionManager.OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + Log.i(TAG, "onSubscriptionsChanged()"); + sendMsg(mAudioHandler, MSG_CONFIGURATION_CHANGED, SENDMSG_REPLACE, + 0, 0, null, 0); + } + }; + /** * Initialize intent receives and settings observers for this service. * Must be called after createStreamStates() as the handling of some events @@ -1388,6 +1403,13 @@ public class AudioService extends IAudioService.Stub mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null, Context.RECEIVER_EXPORTED); + SubscriptionManager subscriptionManager = mContext.getSystemService( + SubscriptionManager.class); + if (subscriptionManager == null) { + Log.e(TAG, "initExternalEventReceivers cannot create SubscriptionManager!"); + } else { + subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener); + } } public void systemReady() { @@ -3665,7 +3687,7 @@ public class AudioService extends IAudioService.Stub for (int stream = 0; stream < mStreamStates.length; stream++) { VolumeStreamState vss = mStreamStates[stream]; if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) { - if (!(readCameraSoundForced() + if (!(mCameraSoundForced && (vss.getStreamType() == AudioSystem.STREAM_SYSTEM_ENFORCED))) { boolean changed = vss.mute(state, /* apply= */ false); @@ -9237,6 +9259,10 @@ public class AudioService extends IAudioService.Stub onLowerVolumeToRs1(); break; + case MSG_CONFIGURATION_CHANGED: + onConfigurationChanged(); + break; + default: if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) { // msg could be for the SoundDoseHelper @@ -9419,7 +9445,12 @@ public class AudioService extends IAudioService.Stub } AudioSystem.setParameters("screen_state=off"); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { - handleConfigurationChanged(context); + sendMsg(mAudioHandler, + MSG_CONFIGURATION_CHANGED, + SENDMSG_REPLACE, + 0, + 0, + null, 0); } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { if (mUserSwitchedReceived) { // attempt to stop music playback for background user except on first user @@ -10161,10 +10192,30 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== + + // camera sound is forced if any of the resources corresponding to one active SIM + // demands it. private boolean readCameraSoundForced() { - return SystemProperties.getBoolean("audio.camerasound.force", false) || - mContext.getResources().getBoolean( - com.android.internal.R.bool.config_camera_sound_forced); + if (SystemProperties.getBoolean("audio.camerasound.force", false) + || mContext.getResources().getBoolean( + com.android.internal.R.bool.config_camera_sound_forced)) { + return true; + } + + SubscriptionManager subscriptionManager = mContext.getSystemService( + SubscriptionManager.class); + if (subscriptionManager == null) { + Log.e(TAG, "readCameraSoundForced cannot create SubscriptionManager!"); + return false; + } + int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(false); + for (int subId : subscriptionIds) { + if (SubscriptionManager.getResourcesForSubId(mContext, subId).getBoolean( + com.android.internal.R.bool.config_camera_sound_forced)) { + return true; + } + } + return false; } //========================================================================================== @@ -10375,11 +10426,11 @@ public class AudioService extends IAudioService.Stub * Monitoring rotation is optional, and is defined by the definition and value * of the "ro.audio.monitorRotation" system property. */ - private void handleConfigurationChanged(Context context) { + private void onConfigurationChanged() { try { // reading new configuration "safely" (i.e. under try catch) in case anything // goes wrong. - Configuration config = context.getResources().getConfiguration(); + Configuration config = mContext.getResources().getConfiguration(); mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG); boolean cameraSoundForced = readCameraSoundForced(); @@ -10406,7 +10457,7 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, cameraSoundForced ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, - "handleConfigurationChanged"); + "onConfigurationChanged"); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index e12cd8c9a43b..656882f3f615 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1056,10 +1056,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } float userLux = BrightnessMappingStrategy.NO_USER_LUX; - float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + float userNits = -1; if (mInteractiveModeBrightnessMapper != null) { userLux = mInteractiveModeBrightnessMapper.getUserLux(); - userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness(); + float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness(); + userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness); } final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( @@ -1179,6 +1180,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } + float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + if (userNits >= 0) { + userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits); + if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) { + userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + } + } mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController( this, handler.getLooper(), mSensorManager, mLightSensor, mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index fbc354eb4c11..3e01222bbae6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -893,10 +893,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } float userLux = BrightnessMappingStrategy.NO_USER_LUX; - float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + float userNits = -1; if (mInteractiveModeBrightnessMapper != null) { userLux = mInteractiveModeBrightnessMapper.getUserLux(); - userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness(); + float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness(); + userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness); } final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( @@ -1016,6 +1017,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.stop(); } + float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + if (userNits >= 0) { + userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits); + if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) { + userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS; + } + } mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController( this, handler.getLooper(), mSensorManager, mLightSensor, mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 8d0689ff8fe5..79984c9b5355 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -911,7 +911,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { final float newHdrSdrRatio; if (displayNits != DisplayDeviceConfig.NITS_INVALID && sdrNits != DisplayDeviceConfig.NITS_INVALID) { - newHdrSdrRatio = displayNits / sdrNits; + // Ensure the ratio stays >= 1.0f as values below that are nonsensical + newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits); } else { newHdrSdrRatio = Float.NaN; } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 1aee345f96d4..f107d0bf9932 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -106,6 +106,14 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_HELP: onHelp(); return 0; + case COMMAND_GET_DISABLED: + runGetDisabled(); + return 0; + case COMMAND_SET_DISABLED: + // Note: if the user has an LSKF, then this has no immediate effect but instead + // just ensures the lockscreen will be disabled later when the LSKF is cleared. + runSetDisabled(); + return 0; } if (!checkCredential()) { return -1; @@ -124,15 +132,9 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_CLEAR: success = runClear(); break; - case COMMAND_SET_DISABLED: - runSetDisabled(); - break; case COMMAND_VERIFY: runVerify(); break; - case COMMAND_GET_DISABLED: - runGetDisabled(); - break; default: getErrPrintWriter().println("Unknown command: " + cmd); break; diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 2e86df89f63e..f95f7bc0d165 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import static com.android.server.pm.DexOptHelper.useArtService; import static com.android.server.pm.PackageManagerService.TAG; import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; @@ -245,7 +244,7 @@ public class AppDataHelper { } } - if (!useArtService()) { // ART Service handles this on demand instead. + if (!DexOptHelper.useArtService()) { // ART Service handles this on demand instead. // Prepare the application profiles only for upgrades and // first boot (so that we don't repeat the same operation at // each boot). @@ -591,7 +590,7 @@ public class AppDataHelper { Slog.wtf(TAG, "Package was null!", new Throwable()); return; } - if (useArtService()) { + if (DexOptHelper.useArtService()) { destroyAppProfilesWithArtService(pkg); } else { try { @@ -637,7 +636,7 @@ public class AppDataHelper { } private void destroyAppProfilesLeafLIF(AndroidPackage pkg) { - if (useArtService()) { + if (DexOptHelper.useArtService()) { destroyAppProfilesWithArtService(pkg); } else { try { @@ -651,6 +650,15 @@ public class AppDataHelper { } private void destroyAppProfilesWithArtService(AndroidPackage pkg) { + if (!DexOptHelper.artManagerLocalIsInitialized()) { + // This function may get called while PackageManagerService is constructed (via e.g. + // InitAppsHelper.initSystemApps), and ART Service hasn't yet been started then (it + // requires a registered PackageManagerLocal instance). We can skip clearing any stale + // app profiles in this case, because ART Service and the runtime will ignore stale or + // otherwise invalid ref and cur profiles. + return; + } + try (PackageManagerLocal.FilteredSnapshot snapshot = getPackageManagerLocal().withFilteredSnapshot()) { try { diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index a9d4115b4b79..064be7c5ddc7 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -99,6 +99,8 @@ import java.util.function.Predicate; public final class DexOptHelper { private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; + private static boolean sArtManagerLocalIsInitialized = false; + private final PackageManagerService mPm; // Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is @@ -1035,6 +1037,7 @@ public final class DexOptHelper { artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, pm.getDexOptHelper().new DexoptDoneHandler()); LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager); + sArtManagerLocalIsInitialized = true; // Schedule the background job when boot is complete. This decouples us from when // JobSchedulerService is initialized. @@ -1048,6 +1051,15 @@ public final class DexOptHelper { } /** + * Returns true if an {@link ArtManagerLocal} instance has been created. + * + * Avoid this function if at all possible, because it may hide initialization order problems. + */ + public static boolean artManagerLocalIsInitialized() { + return sArtManagerLocalIsInitialized; + } + + /** * Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error. */ public static @NonNull ArtManagerLocal getArtManagerLocal() { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 0d417e457509..68c8abf0c2d3 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -821,8 +821,10 @@ public class Installer extends SystemService { * Creates an oat dir for given package and instruction set. */ public void createOatDir(String packageName, String oatDir, String dexInstructionSet) - throws InstallerException, LegacyDexoptDisabledException { - checkLegacyDexoptDisabled(); + throws InstallerException { + // This method should be allowed even if ART Service is enabled, because it's used for + // creating oat dirs before creating hard links for partial installation. + // TODO(b/274658735): Add an ART Service API to support hard linking. if (!checkBeforeRemote()) return; try { mInstalld.createOatDir(packageName, oatDir, dexInstructionSet); @@ -1177,7 +1179,7 @@ public class Installer extends SystemService { // TODO(b/260124949): Remove the legacy dexopt code paths, i.e. this exception and all code // that may throw it. public LegacyDexoptDisabledException() { - super("Invalid call to legacy dexopt installd method while ART Service is in use."); + super("Invalid call to legacy dexopt method while ART Service is in use."); } } diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index 7f7a23419dda..83d2f6ae0e40 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -132,14 +132,15 @@ final class PackageHandler extends Handler { // Not found or complete. break; } - if (!streaming && state.timeoutExtended()) { + + final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj; + if (!streaming && state.timeoutExtended(response.callerUid)) { // Timeout extended. break; } - final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj; - VerificationUtils.processVerificationResponse(verificationId, state, response, - "Verification timed out", mPm); + VerificationUtils.processVerificationResponseOnTimeout(verificationId, state, + response, mPm); break; } @@ -195,8 +196,7 @@ final class PackageHandler extends Handler { } final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj; - VerificationUtils.processVerificationResponse(verificationId, state, response, - "Install not allowed", mPm); + VerificationUtils.processVerificationResponse(verificationId, state, response, mPm); break; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 03e0d360f9e3..36aeca142f5c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -49,7 +49,6 @@ import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeByteArrayAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; import static com.android.internal.util.XmlUtils.writeUriAttribute; -import static com.android.server.pm.DexOptHelper.useArtService; import static com.android.server.pm.PackageInstallerService.prepareStageDir; import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME; @@ -173,7 +172,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.pm.Installer.InstallerException; -import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -2560,15 +2558,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (isLinkPossible(fromFiles, toDir)) { - if (!useArtService()) { // ART Service creates oat dirs on demand instead. - if (!mResolvedInstructionSets.isEmpty()) { - final File oatDir = new File(toDir, "oat"); - try { - createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir); - } catch (LegacyDexoptDisabledException e) { - throw new RuntimeException(e); - } - } + if (!mResolvedInstructionSets.isEmpty()) { + final File oatDir = new File(toDir, "oat"); + createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir); } // pre-create lib dirs for linking if necessary if (!mResolvedNativeLibPaths.isEmpty()) { @@ -3829,7 +3821,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void createOatDirs(String packageName, List<String> instructionSets, File fromDir) - throws PackageManagerException, LegacyDexoptDisabledException { + throws PackageManagerException { for (String instructionSet : instructionSets) { try { mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6bc876037cfb..a6faff85ab06 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4885,14 +4885,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService mHandler.post(() -> { final int id = verificationId >= 0 ? verificationId : -verificationId; final PackageVerificationState state = mPendingVerification.get(id); - if (state == null || state.timeoutExtended() || !state.checkRequiredVerifierUid( - callingUid)) { - // Only allow calls from required verifiers. + if (state == null || !state.extendTimeout(callingUid)) { + // Invalid uid or already extended. return; } - state.extendTimeout(); - final PackageVerificationResponse response = new PackageVerificationResponse( verificationCodeAtTimeout, callingUid); @@ -5561,32 +5558,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule, IDexModuleRegisterCallback callback) { - if (useArtService()) { - // ART Service currently doesn't support this explicit dexopting and instead relies - // on background dexopt for secondary dex files. This API is problematic since it - // doesn't provide the correct classloader context. - Slog.i(TAG, - "Ignored unsupported registerDexModule call for " + dexModulePath + " in " - + packageName); - return; - } - - int userId = UserHandle.getCallingUserId(); - ApplicationInfo ai = snapshot().getApplicationInfo(packageName, /*flags*/ 0, userId); - DexManager.RegisterDexModuleResult result; - if (ai == null) { - Slog.w(PackageManagerService.TAG, - "Registering a dex module for a package that does not exist for the" + - " calling user. package=" + packageName + ", user=" + userId); - result = new DexManager.RegisterDexModuleResult(false, "Package not installed"); - } else { - try { - result = mDexManager.registerDexModule( - ai, dexModulePath, isSharedModule, userId); - } catch (LegacyDexoptDisabledException e) { - throw new RuntimeException(e); - } - } + // ART Service doesn't support this explicit dexopting and instead relies on background + // dexopt for secondary dex files. For compat parity between ART Service and the legacy + // code it's disabled for both. + // + // Also, this API is problematic anyway since it doesn't provide the correct classloader + // context, so it is hard to produce dexopt artifacts that the runtime can load + // successfully. + Slog.i(TAG, + "Ignored unsupported registerDexModule call for " + dexModulePath + " in " + + packageName); + DexManager.RegisterDexModuleResult result = new DexManager.RegisterDexModuleResult( + false, "registerDexModule call not supported since Android U"); if (callback != null) { mHandler.post(() -> { diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java index 929bc1e0b3c4..0b6ccc41d956 100644 --- a/services/core/java/com/android/server/pm/PackageVerificationState.java +++ b/services/core/java/com/android/server/pm/PackageVerificationState.java @@ -33,6 +33,8 @@ class PackageVerificationState { private final SparseBooleanArray mRequiredVerifierUids; private final SparseBooleanArray mUnrespondedRequiredVerifierUids; + private final SparseBooleanArray mExtendedTimeoutUids; + private boolean mSufficientVerificationComplete; private boolean mSufficientVerificationPassed; @@ -41,8 +43,6 @@ class PackageVerificationState { private boolean mRequiredVerificationPassed; - private boolean mExtendedTimeout; - private boolean mIntegrityVerificationComplete; /** @@ -54,9 +54,9 @@ class PackageVerificationState { mSufficientVerifierUids = new SparseBooleanArray(); mRequiredVerifierUids = new SparseBooleanArray(); mUnrespondedRequiredVerifierUids = new SparseBooleanArray(); + mExtendedTimeoutUids = new SparseBooleanArray(); mRequiredVerificationComplete = false; mRequiredVerificationPassed = true; - mExtendedTimeout = false; } VerifyingSession getVerifyingSession() { @@ -88,14 +88,27 @@ class PackageVerificationState { return mSufficientVerifierUids.get(uid, false); } + void setVerifierResponseOnTimeout(int uid, int code) { + if (!checkRequiredVerifierUid(uid)) { + return; + } + + // Timeout, not waiting for the sufficient verifiers anymore. + mSufficientVerifierUids.clear(); + + // Only if unresponded. + if (mUnrespondedRequiredVerifierUids.get(uid, false)) { + setVerifierResponse(uid, code); + } + } + /** * Should be called when a verification is received from an agent so the state of the package * verification can be tracked. * * @param uid user ID of the verifying agent - * @return {@code true} if the verifying agent actually exists in our list */ - boolean setVerifierResponse(int uid, int code) { + void setVerifierResponse(int uid, int code) { if (mRequiredVerifierUids.get(uid)) { switch (code) { case PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT: @@ -109,13 +122,19 @@ class PackageVerificationState { break; default: mRequiredVerificationPassed = false; + // Required verifier rejected, no need to wait for the rest. + mUnrespondedRequiredVerifierUids.clear(); + mSufficientVerifierUids.clear(); + mExtendedTimeoutUids.clear(); } + // Responded, no need to extend timeout. + mExtendedTimeoutUids.delete(uid); + mUnrespondedRequiredVerifierUids.delete(uid); if (mUnrespondedRequiredVerifierUids.size() == 0) { mRequiredVerificationComplete = true; } - return true; } else if (mSufficientVerifierUids.get(uid)) { if (code == PackageManager.VERIFICATION_ALLOW) { mSufficientVerificationPassed = true; @@ -126,11 +145,7 @@ class PackageVerificationState { if (mSufficientVerifierUids.size() == 0) { mSufficientVerificationComplete = true; } - - return true; } - - return false; } /** @@ -181,10 +196,12 @@ class PackageVerificationState { } /** Extend the timeout for this Package to be verified. */ - void extendTimeout() { - if (!mExtendedTimeout) { - mExtendedTimeout = true; + boolean extendTimeout(int uid) { + if (!checkRequiredVerifierUid(uid) || timeoutExtended(uid)) { + return false; } + mExtendedTimeoutUids.append(uid, true); + return true; } /** @@ -192,8 +209,8 @@ class PackageVerificationState { * * @return {@code true} if a timeout was already extended. */ - boolean timeoutExtended() { - return mExtendedTimeout; + boolean timeoutExtended(int uid) { + return mExtendedTimeoutUids.get(uid, false); } void setIntegrityVerificationResult(int code) { diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java index 30f2132ce1f1..f0610180040c 100644 --- a/services/core/java/com/android/server/pm/VerificationUtils.java +++ b/services/core/java/com/android/server/pm/VerificationUtils.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.server.pm.PackageManagerService.PACKAGE_MIME_TYPE; import static com.android.server.pm.PackageManagerService.TAG; @@ -32,6 +33,8 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + final class VerificationUtils { /** * The default maximum time to wait for the verification agent to return in @@ -97,39 +100,63 @@ final class VerificationUtils { android.Manifest.permission.PACKAGE_VERIFICATION_AGENT); } + @VisibleForTesting(visibility = PACKAGE) + static void processVerificationResponseOnTimeout(int verificationId, + PackageVerificationState state, PackageVerificationResponse response, + PackageManagerService pms) { + state.setVerifierResponseOnTimeout(response.callerUid, response.code); + processVerificationResponse(verificationId, state, response.code, "Verification timed out", + pms); + } + + @VisibleForTesting(visibility = PACKAGE) static void processVerificationResponse(int verificationId, PackageVerificationState state, - PackageVerificationResponse response, String failureReason, PackageManagerService pms) { + PackageVerificationResponse response, PackageManagerService pms) { state.setVerifierResponse(response.callerUid, response.code); + processVerificationResponse(verificationId, state, response.code, "Install not allowed", + pms); + } + + private static void processVerificationResponse(int verificationId, + PackageVerificationState state, int verificationResult, String failureReason, + PackageManagerService pms) { if (!state.isVerificationComplete()) { return; } final VerifyingSession verifyingSession = state.getVerifyingSession(); - final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile); + final Uri originUri = verifyingSession != null ? Uri.fromFile( + verifyingSession.mOriginInfo.mResolvedFile) : null; final int verificationCode = - state.isInstallAllowed() ? response.code : PackageManager.VERIFICATION_REJECT; + state.isInstallAllowed() ? verificationResult : PackageManager.VERIFICATION_REJECT; - VerificationUtils.broadcastPackageVerified(verificationId, originUri, - verificationCode, null, - verifyingSession.getDataLoaderType(), verifyingSession.getUser(), - pms.mContext); + if (pms != null && verifyingSession != null) { + VerificationUtils.broadcastPackageVerified(verificationId, originUri, + verificationCode, null, + verifyingSession.getDataLoaderType(), verifyingSession.getUser(), + pms.mContext); + } if (state.isInstallAllowed()) { Slog.i(TAG, "Continuing with installation of " + originUri); } else { String errorMsg = failureReason + " for " + originUri; Slog.i(TAG, errorMsg); - verifyingSession.setReturnCode( - PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg); + if (verifyingSession != null) { + verifyingSession.setReturnCode( + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg); + } } - if (state.areAllVerificationsComplete()) { + if (pms != null && state.areAllVerificationsComplete()) { pms.mPendingVerification.remove(verificationId); } Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId); - verifyingSession.handleVerificationFinished(); + if (verifyingSession != null) { + verifyingSession.handleVerificationFinished(); + } } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 7f0c3f9f4f06..6e738daf9315 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -16,7 +16,6 @@ package com.android.server.pm.dex; -import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; @@ -659,62 +658,6 @@ public class DexManager { } } - // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the - // compilation happening here will use a pessimistic context. - public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, - boolean isSharedModule, int userId) throws LegacyDexoptDisabledException { - // Find the owning package record. - DexSearchResult searchResult = getDexPackage(info, dexPath, userId); - - if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) { - return new RegisterDexModuleResult(false, "Package not found"); - } - if (!info.packageName.equals(searchResult.mOwningPackageName)) { - return new RegisterDexModuleResult(false, "Dex path does not belong to package"); - } - if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || - searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) { - return new RegisterDexModuleResult(false, "Main apks cannot be registered"); - } - - // We found the package. Now record the usage for all declared ISAs. - boolean update = false; - // If this is a shared module set the loading package to an arbitrary package name - // so that we can mark that module as usedByOthers. - String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName; - for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { - boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, userId, isa, /*primaryOrSplit*/ false, - loadingPackage, - PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, - /*overwriteCLC=*/ false); - update |= newUpdate; - } - if (update) { - mPackageDexUsage.maybeWriteAsync(); - } - - DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) - .getDexUseInfoMap().get(dexPath); - - // Try to optimize the package according to the install reason. - DexoptOptions options = new DexoptOptions(info.packageName, - PackageManagerService.REASON_INSTALL, /*flags*/0); - - int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, - options); - - // If we fail to optimize the package log an error but don't propagate the error - // back to the app. The app cannot do much about it and the background job - // will rety again when it executes. - // TODO(calin): there might be some value to return the error here but it may - // cause red herrings since that doesn't mean the app cannot use the module. - if (result != PackageDexOptimizer.DEX_OPT_FAILED) { - Slog.e(TAG, "Failed to optimize dex module " + dexPath); - } - return new RegisterDexModuleResult(true, "Dex module registered successfully"); - } - /** * Return all packages that contain records of secondary dex files. */ diff --git a/services/core/java/com/android/server/powerstats/BatteryTrigger.java b/services/core/java/com/android/server/powerstats/BatteryTrigger.java index b35cb52d5025..15c181198fae 100644 --- a/services/core/java/com/android/server/powerstats/BatteryTrigger.java +++ b/services/core/java/com/android/server/powerstats/BatteryTrigger.java @@ -59,7 +59,9 @@ public final class BatteryTrigger extends PowerStatsLogTrigger { if (triggerEnabled) { IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = mContext.registerReceiver(mBatteryLevelReceiver, filter); - mBatteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + if (batteryStatus != null) { + mBatteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + } } } } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 06d108b20b93..86aca3abeee5 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -88,6 +88,10 @@ import java.util.Set; public class DisplayRotation { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM; + // Delay in milliseconds when updating config due to folding events. This prevents + // config changes and unexpected jumps while folding the device to closed state. + private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800; + private static class RotationAnimationPair { @AnimRes int mEnter; @@ -1662,6 +1666,7 @@ public class DisplayRotation { private boolean mInHalfFoldTransition = false; private final boolean mIsDisplayAlwaysSeparatingHinge; private final Set<Integer> mTabletopRotations; + private final Runnable mActivityBoundsUpdateCallback; FoldController() { mTabletopRotations = new ArraySet<>(); @@ -1696,6 +1701,26 @@ public class DisplayRotation { } mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean( R.bool.config_isDisplayHingeAlwaysSeparating); + + mActivityBoundsUpdateCallback = new Runnable() { + public void run() { + if (mDeviceState == DeviceStateController.DeviceState.OPEN + || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) { + synchronized (mLock) { + final Task topFullscreenTask = + mDisplayContent.getTask( + t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + if (topFullscreenTask != null) { + final ActivityRecord top = + topFullscreenTask.topRunningActivity(); + if (top != null) { + top.recomputeConfiguration(); + } + } + } + } + } + }; } boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) { @@ -1767,14 +1792,9 @@ public class DisplayRotation { false /* forceRelayout */); } // Alert the activity of possible new bounds. - final Task topFullscreenTask = - mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - if (topFullscreenTask != null) { - final ActivityRecord top = topFullscreenTask.topRunningActivity(); - if (top != null) { - top.recomputeConfiguration(); - } - } + UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback); + UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback, + FOLDING_RECOMPUTE_CONFIG_DELAY_MS); } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8f40e79d7c0f..0b960ec2a583 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -419,8 +419,12 @@ final class InputMonitor { if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) { requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, recentsAnimationInputConsumer.mName); + } + if (mDisplayContent.mInputMethodWindow != null + && mDisplayContent.mInputMethodWindow.isVisible()) { // Hiding IME/IME icon when recents input consumer gain focus. - if (!mDisplayContent.isImeAttachedToApp()) { + final boolean isImeAttachedToApp = mDisplayContent.isImeAttachedToApp(); + if (!isImeAttachedToApp) { // Hiding IME if IME window is not attached to app since it's not proper to // snapshot Task with IME window to animate together in this case. final InputMethodManagerInternal inputMethodManagerInternal = @@ -429,6 +433,14 @@ final class InputMonitor { inputMethodManagerInternal.hideCurrentInputMethod( SoftInputShowHideReason.HIDE_RECENTS_ANIMATION); } + // Ensure removing the IME snapshot when the app no longer to show on the + // task snapshot (also taking the new task snaphot to update the overview). + final ActivityRecord app = mDisplayContent.getImeInputTarget() != null + ? mDisplayContent.getImeInputTarget().getActivityRecord() : null; + if (app != null) { + mDisplayContent.removeImeSurfaceImmediately(); + mDisplayContent.mAtmService.takeTaskSnapshot(app.getTask().mTaskId); + } } else { // Disable IME icon explicitly when IME attached to the app in case // IME icon might flickering while swiping to the next app task still diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 93c8c3666706..184293e11002 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -390,7 +390,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean taskAppearedSent = t.mTaskAppearedSent; if (taskAppearedSent) { if (t.getSurfaceControl() != null) { - t.migrateToNewSurfaceControl(t.getSyncTransaction()); + t.migrateToNewSurfaceControl(t.getPendingTransaction()); } t.mTaskAppearedSent = false; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index ba49dd0032a4..28cbe075a25f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2732,9 +2732,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { buffer, screenshotBuffer.getColorSpace()); } SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); - - t.setBuffer(snapshotSurface, buffer); - t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace()); + TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer); t.show(snapshotSurface); // Place it on top of anything else in the container. diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java index fd31b2211b7a..d559b67218ca 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java @@ -84,9 +84,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -2904,108 +2901,6 @@ public class PackageManagerTests extends AndroidTestCase { PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - private static class TestDexModuleRegisterCallback - extends PackageManager.DexModuleRegisterCallback { - private String mDexModulePath = null; - private boolean mSuccess = false; - private String mMessage = null; - CountDownLatch doneSignal = new CountDownLatch(1); - - @Override - public void onDexModuleRegistered(String dexModulePath, boolean success, String message) { - mDexModulePath = dexModulePath; - mSuccess = success; - mMessage = message; - doneSignal.countDown(); - } - - boolean waitTillDone() { - long startTime = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) { - try { - return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Log.i(TAG, "Interrupted during sleep", e); - } - } - return false; - } - - } - - // Verify that the base code path cannot be registered. - public void testRegisterDexModuleBaseCode() throws Exception { - PackageManager pm = getPm(); - ApplicationInfo info = getContext().getApplicationInfo(); - TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback(); - pm.registerDexModule(info.getBaseCodePath(), callback); - assertTrue(callback.waitTillDone()); - assertEquals(info.getBaseCodePath(), callback.mDexModulePath); - assertFalse("BaseCodePath should not be registered", callback.mSuccess); - } - - // Verify that modules which are not own by the calling package are not registered. - public void testRegisterDexModuleNotOwningModule() throws Exception { - TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback(); - String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk"; - getPm().registerDexModule(moduleBelongingToOtherPackage, callback); - assertTrue(callback.waitTillDone()); - assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath); - assertTrue(callback.waitTillDone()); - assertFalse("Only modules belonging to the calling package can be registered", - callback.mSuccess); - } - - // Verify that modules owned by the package are successfully registered. - public void testRegisterDexModuleSuccessfully() throws Exception { - ApplicationInfo info = getContext().getApplicationInfo(); - // Copy the main apk into the data folder and use it as a "module". - File dexModuleDir = new File(info.dataDir, "module-dir"); - File dexModule = new File(dexModuleDir, "module.apk"); - try { - assertNotNull(FileUtils.createDir( - dexModuleDir.getParentFile(), dexModuleDir.getName())); - Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(), - StandardCopyOption.REPLACE_EXISTING); - TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback(); - getPm().registerDexModule(dexModule.toString(), callback); - assertTrue(callback.waitTillDone()); - assertEquals(dexModule.toString(), callback.mDexModulePath); - assertTrue(callback.waitTillDone()); - assertTrue(callback.mMessage, callback.mSuccess); - - // NOTE: - // This actually verifies internal behaviour which might change. It's not - // ideal but it's the best we can do since there's no other place we can currently - // write a better test. - for(String isa : getAppDexInstructionSets(info)) { - Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex")); - Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex")); - } - } finally { - FileUtils.deleteContentsAndDir(dexModuleDir); - } - } - - // If the module does not exist on disk we should get a failure. - public void testRegisterDexModuleNotExists() throws Exception { - ApplicationInfo info = getContext().getApplicationInfo(); - String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString(); - TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback(); - getPm().registerDexModule(nonExistentApk, callback); - assertTrue(callback.waitTillDone()); - assertEquals(nonExistentApk, callback.mDexModulePath); - assertTrue(callback.waitTillDone()); - assertFalse("DexModule registration should fail", callback.mSuccess); - } - - // If the module does not exist on disk we should get a failure. - public void testRegisterDexModuleNotExistsNoCallback() throws Exception { - ApplicationInfo info = getContext().getApplicationInfo(); - String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString(); - getPm().registerDexModule(nonExistentApk, null); - } - @LargeTest public void testMinInstallableTargetSdkPass() throws Exception { // Test installing a package that meets the minimum installable sdk requirement diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java index 8715afda5cee..a93e8ad93756 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java @@ -95,9 +95,13 @@ public class PackageVerificationStateTest extends AndroidTestCase { state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT); - assertFalse("Verification should not be marked as complete yet", + assertTrue("Verification should be considered complete now", state.isVerificationComplete()); + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + + // Nothing changes. state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_REJECT); assertTrue("Verification should be considered complete now", @@ -117,9 +121,13 @@ public class PackageVerificationStateTest extends AndroidTestCase { state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT); - assertFalse("Verification should not be marked as complete yet", + assertTrue("Verification should be considered complete now", state.isVerificationComplete()); + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + + // Nothing changes. state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW); assertTrue("Verification should be considered complete now", @@ -151,6 +159,162 @@ public class PackageVerificationStateTest extends AndroidTestCase { state.isInstallAllowed()); } + public void testPackageVerificationState_TwoRequiredVerifiers_SecondTimesOut_DefaultAllow() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Timeout with default ALLOW. + processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_2, true); + } + + public void testPackageVerificationState_TwoRequiredVerifiers_SecondTimesOut_DefaultReject() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Timeout with default REJECT. + processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_2, false); + } + + public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_DefaultAllow() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Timeout with default ALLOW. + processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertTrue("Installation should be marked as allowed", + state.isInstallAllowed()); + } + + public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_DefaultReject() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Timeout with default REJECT. + processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + + // Nothing changes. + state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + } + + public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_SecondExtends_DefaultAllow() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.extendTimeout(REQUIRED_UID_2); + + // Timeout with default ALLOW. + processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + assertTrue("Timeout is extended", + state.timeoutExtended(REQUIRED_UID_2)); + + state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertTrue("Installation should be marked as allowed", + state.isInstallAllowed()); + } + + public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_SecondExtends_DefaultReject() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + state.addRequiredVerifierUid(REQUIRED_UID_2); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.extendTimeout(REQUIRED_UID_2); + + // Timeout with default REJECT. + processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1); + + assertFalse("Timeout should not be extended for this verifier", + state.timeoutExtended(REQUIRED_UID_2)); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + + // Nothing changes. + state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + + assertFalse("Installation should be marked as denied", + state.isInstallAllowed()); + } + public void testPackageVerificationState_RequiredAndOneSufficient_RequiredDeniedInstall() { PackageVerificationState state = new PackageVerificationState(null); state.addRequiredVerifierUid(REQUIRED_UID_1); @@ -231,6 +395,66 @@ public class PackageVerificationStateTest extends AndroidTestCase { state.isInstallAllowed()); } + public void testPackageVerificationState_RequiredAllow_SufficientTimesOut_DefaultAllow() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Required allows. + state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); + + // Timeout with default ALLOW. + processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1, true); + } + + public void testPackageVerificationState_RequiredExtendAllow_SufficientTimesOut_DefaultAllow() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Extend first. + state.extendTimeout(REQUIRED_UID_1); + + // Required allows. + state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); + + // Timeout with default ALLOW. + processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1, true); + } + + public void testPackageVerificationState_RequiredAllow_SufficientTimesOut_DefaultReject() { + PackageVerificationState state = new PackageVerificationState(null); + state.addRequiredVerifierUid(REQUIRED_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + state.addSufficientVerifier(SUFFICIENT_UID_1); + + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + + // Required allows. + state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW); + + // Timeout with default REJECT. + processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1, true); + } + public void testPackageVerificationState_RequiredAndTwoSufficient_OneSufficientIsEnough() { PackageVerificationState state = new PackageVerificationState(null); state.addRequiredVerifierUid(REQUIRED_UID_1); @@ -400,4 +624,25 @@ public class PackageVerificationStateTest extends AndroidTestCase { assertFalse(state.areAllVerificationsComplete()); } + + private void processOnTimeout(PackageVerificationState state, int code, int uid) { + // CHECK_PENDING_VERIFICATION handler. + assertFalse("Verification should not be marked as complete yet", + state.isVerificationComplete()); + assertFalse("Timeout should not be extended for this verifier", + state.timeoutExtended(uid)); + + PackageVerificationResponse response = new PackageVerificationResponse(code, uid); + VerificationUtils.processVerificationResponseOnTimeout(-1, state, response, null); + } + + private void processOnTimeout(PackageVerificationState state, int code, int uid, + boolean expectAllow) { + processOnTimeout(state, code, uid); + + assertTrue("Verification should be considered complete now", + state.isVerificationComplete()); + assertEquals("Installation should be marked as " + (expectAllow ? "allowed" : "rejected"), + expectAllow, state.isInstallAllowed()); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index f9f53251fa7e..a5adf3f9bf80 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2778,51 +2778,70 @@ public final class AlarmManagerServiceTest { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent()); } - final String otherUidPackage1 = "other.uid.package1"; - final String otherUidPackage2 = "other.uid.package2"; - final int otherUid = 1243; + final String otherPackage1 = "other.package1"; + final String otherPackage2 = "other.package2"; + final int otherAppId = 1243; + final int otherUser1 = 31; + final int otherUser2 = 8; + final int otherUid1 = UserHandle.getUid(otherUser1, otherAppId); + final int otherUid2 = UserHandle.getUid(otherUser2, otherAppId); registerAppIds( - new String[]{TEST_CALLING_PACKAGE, otherUidPackage1, otherUidPackage2}, - new Integer[]{TEST_CALLING_UID, otherUid, otherUid} + new String[]{TEST_CALLING_PACKAGE, otherPackage1, otherPackage2}, + new Integer[]{UserHandle.getAppId(TEST_CALLING_UID), otherAppId, otherAppId} ); for (int i = 0; i < 9; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0, - getNewMockPendingIntent(otherUid, otherUidPackage1), 0, 0, otherUid, - otherUidPackage1, null); + getNewMockPendingIntent(otherUid1, otherPackage1), 0, 0, otherUid1, + otherPackage1, null); } for (int i = 0; i < 8; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0, - getNewMockPendingIntent(otherUid, otherUidPackage2), 0, 0, otherUid, - otherUidPackage2, null); + getNewMockPendingIntent(otherUid1, otherPackage2), 0, 0, otherUid1, + otherPackage2, null); } - assertEquals(27, mService.mAlarmStore.size()); + for (int i = 0; i < 7; i++) { + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 28, 0, + getNewMockPendingIntent(otherUid2, otherPackage2), 0, 0, otherUid2, + otherPackage2, null); + } + + assertEquals(34, mService.mAlarmStore.size()); try { - mBinder.removeAll(otherUidPackage1); + mBinder.removeAll(otherPackage1); fail("removeAll() for wrong package did not throw SecurityException"); } catch (SecurityException se) { // Expected } try { - mBinder.removeAll(otherUidPackage2); + mBinder.removeAll(otherPackage2); fail("removeAll() for wrong package did not throw SecurityException"); } catch (SecurityException se) { // Expected } mBinder.removeAll(TEST_CALLING_PACKAGE); - assertEquals(17, mService.mAlarmStore.size()); + assertEquals(24, mService.mAlarmStore.size()); assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE))); - mTestCallingUid = otherUid; - mBinder.removeAll(otherUidPackage1); - assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage1))); - assertEquals(8, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage2))); + mTestCallingUid = otherUid1; + mBinder.removeAll(otherPackage1); + assertEquals(15, mService.mAlarmStore.size()); + assertEquals(15, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2))); + assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherPackage1))); + + mBinder.removeAll(otherPackage2); + assertEquals(7, mService.mAlarmStore.size()); + assertEquals(7, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2))); + + mTestCallingUid = otherUid2; + mBinder.removeAll(otherPackage2); + assertEquals(0, mService.mAlarmStore.size()); } @Test @@ -3856,4 +3875,52 @@ public final class AlarmManagerServiceTest { assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED); assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2)); } + + @Test + public void lookForPackageLocked() throws Exception { + final String package2 = "test.package.2"; + final int uid2 = 359712; + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 10, getNewMockPendingIntent()); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, + getNewMockPendingIntent(uid2, package2)); + + doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); + + assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + assertTrue(mService.lookForPackageLocked(package2, uid2)); + + mNowElapsedTest += 10; // Advance time past the first alarm only. + mTestTimer.expire(); + + assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + assertTrue(mService.lookForPackageLocked(package2, uid2)); + + // The non-wakeup alarm is sent on interactive state change: false -> true. + mService.interactiveStateChangedLocked(false); + mService.interactiveStateChangedLocked(true); + + assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + assertTrue(mService.lookForPackageLocked(package2, uid2)); + + mNowElapsedTest += 10; // Advance time past the second alarm. + mTestTimer.expire(); + + assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); + assertFalse(mService.lookForPackageLocked(package2, uid2)); + } + + @Test + public void onQueryPackageRestart() { + final String[] packages = {"p1", "p2", "p3"}; + final int uid = 5421; + final Intent packageAdded = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART) + .setData(Uri.fromParts("package", packages[0], null)) + .putExtra(Intent.EXTRA_PACKAGES, packages) + .putExtra(Intent.EXTRA_UID, uid); + mPackageChangesReceiver.onReceive(mMockContext, packageAdded); + + for (String p : packages) { + verify(mService).lookForPackageLocked(p, uid); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 51dcc0323a96..0ab984bd9381 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -21,11 +21,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -672,6 +675,7 @@ public final class DisplayPowerController2Test { @Test public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() { + // New display device setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class), mock(DisplayDeviceConfig.class), /* isEnabled= */ true); @@ -711,6 +715,56 @@ public final class DisplayPowerController2Test { verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); } + @Test + public void testShortTermModelPersistsWhenDisplayDeviceChanges() { + float lux = 2000; + float brightness = 0.4f; + float nits = 500; + when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux); + when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness); + when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits); + when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); + clearInvocations(mHolder.injector); + + // New display device + setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class), + mock(DisplayDeviceConfig.class), /* isEnabled= */ true); + mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY); + advanceTime(1); + + verify(mHolder.injector).getAutomaticBrightnessController( + any(AutomaticBrightnessController.Callbacks.class), + any(Looper.class), + eq(mSensorManagerMock), + any(), + eq(mHolder.brightnessMappingStrategy), + anyInt(), + anyFloat(), + anyFloat(), + anyFloat(), + anyInt(), + anyInt(), + anyLong(), + anyLong(), + anyBoolean(), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + eq(mContextSpy), + any(HighBrightnessModeController.class), + any(BrightnessThrottler.class), + isNull(), + anyInt(), + anyInt(), + eq(lux), + eq(brightness) + ); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -796,9 +850,9 @@ public final class DisplayPowerController2Test { final ScreenOffBrightnessSensorController screenOffBrightnessSensorController = mock(ScreenOffBrightnessSensorController.class); - TestInjector injector = new TestInjector(displayPowerState, animator, + TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, - hysteresisLevels, screenOffBrightnessSensorController); + hysteresisLevels, screenOffBrightnessSensorController)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -816,7 +870,8 @@ public final class DisplayPowerController2Test { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, - screenOffBrightnessSensorController, hbmMetadata); + screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy, + injector); } /** @@ -833,6 +888,8 @@ public final class DisplayPowerController2Test { public final WakelockController wakelockController; public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController; public final HighBrightnessModeMetadata hbmMetadata; + public final BrightnessMappingStrategy brightnessMappingStrategy; + public final DisplayPowerController2.Injector injector; DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display, DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting, @@ -840,7 +897,9 @@ public final class DisplayPowerController2Test { AutomaticBrightnessController automaticBrightnessController, WakelockController wakelockController, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, - HighBrightnessModeMetadata hbmMetadata) { + HighBrightnessModeMetadata hbmMetadata, + BrightnessMappingStrategy brightnessMappingStrategy, + DisplayPowerController2.Injector injector) { this.dpc = dpc; this.display = display; this.displayPowerState = displayPowerState; @@ -850,6 +909,8 @@ public final class DisplayPowerController2Test { this.wakelockController = wakelockController; this.screenOffBrightnessSensorController = screenOffBrightnessSensorController; this.hbmMetadata = hbmMetadata; + this.brightnessMappingStrategy = brightnessMappingStrategy; + this.injector = injector; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 0a1bf1c9ed99..c021ef65a291 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -21,11 +21,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -676,6 +679,7 @@ public final class DisplayPowerControllerTest { @Test public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() { + // New display device setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class), mock(DisplayDeviceConfig.class), /* isEnabled= */ true); @@ -715,6 +719,56 @@ public final class DisplayPowerControllerTest { verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); } + @Test + public void testShortTermModelPersistsWhenDisplayDeviceChanges() { + float lux = 2000; + float brightness = 0.4f; + float nits = 500; + when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux); + when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness); + when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits); + when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); + clearInvocations(mHolder.injector); + + // New display device + setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class), + mock(DisplayDeviceConfig.class), /* isEnabled= */ true); + mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY); + advanceTime(1); + + verify(mHolder.injector).getAutomaticBrightnessController( + any(AutomaticBrightnessController.Callbacks.class), + any(Looper.class), + eq(mSensorManagerMock), + any(), + eq(mHolder.brightnessMappingStrategy), + anyInt(), + anyFloat(), + anyFloat(), + anyFloat(), + anyInt(), + anyInt(), + anyLong(), + anyLong(), + anyBoolean(), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + any(HysteresisLevels.class), + eq(mContextSpy), + any(HighBrightnessModeController.class), + any(BrightnessThrottler.class), + isNull(), + anyInt(), + anyInt(), + eq(lux), + eq(brightness) + ); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -799,9 +853,9 @@ public final class DisplayPowerControllerTest { final ScreenOffBrightnessSensorController screenOffBrightnessSensorController = mock(ScreenOffBrightnessSensorController.class); - DisplayPowerController.Injector injector = new TestInjector(displayPowerState, animator, + DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels, - screenOffBrightnessSensorController); + screenOffBrightnessSensorController)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -819,7 +873,7 @@ public final class DisplayPowerControllerTest { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, screenOffBrightnessSensorController, - hbmMetadata); + hbmMetadata, brightnessMappingStrategy, injector); } /** @@ -835,13 +889,17 @@ public final class DisplayPowerControllerTest { public final AutomaticBrightnessController automaticBrightnessController; public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController; public final HighBrightnessModeMetadata hbmMetadata; + public final BrightnessMappingStrategy brightnessMappingStrategy; + public final DisplayPowerController.Injector injector; DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display, DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting, DualRampAnimator<DisplayPowerState> animator, AutomaticBrightnessController automaticBrightnessController, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, - HighBrightnessModeMetadata hbmMetadata) { + HighBrightnessModeMetadata hbmMetadata, + BrightnessMappingStrategy brightnessMappingStrategy, + DisplayPowerController.Injector injector) { this.dpc = dpc; this.display = display; this.displayPowerState = displayPowerState; @@ -850,6 +908,8 @@ public final class DisplayPowerControllerTest { this.automaticBrightnessController = automaticBrightnessController; this.screenOffBrightnessSensorController = screenOffBrightnessSensorController; this.hbmMetadata = hbmMetadata; + this.brightnessMappingStrategy = brightnessMappingStrategy; + this.injector = injector; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java index d5aa7fec996f..9a7ee4d7887b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java @@ -47,6 +47,7 @@ import android.content.IntentFilter; import android.os.HandlerThread; import android.os.PowerManager; import android.os.Process; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.util.IndentingPrintWriter; @@ -56,6 +57,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -126,6 +128,10 @@ public final class BackgroundDexOptServiceUnitTest { @Before public void setUp() throws Exception { + // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run + // when ART Service is enabled. + Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false)); + when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID); when(mInjector.getContext()).thenReturn(mContext); when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper); diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 9ca287686758..986fb71afa2d 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -29,6 +29,7 @@ android_test { srcs: [ "src/**/*.java", + ":FrameworksCoreTestDoubles-sources", ], static_libs: [ diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java new file mode 100644 index 000000000000..8694094ce6ac --- /dev/null +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java @@ -0,0 +1,181 @@ +/* + * 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.soundtrigger_middleware; + +import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.app.ActivityThread; +import android.media.permission.Identity; +import android.media.permission.IdentityContext; +import android.media.soundtrigger.PhraseRecognitionEvent; +import android.media.soundtrigger.PhraseRecognitionExtra; +import android.media.soundtrigger.RecognitionEvent; +import android.media.soundtrigger.RecognitionStatus; +import android.media.soundtrigger_middleware.ISoundTriggerCallback; +import android.media.soundtrigger_middleware.ISoundTriggerModule; +import android.os.BatteryStatsInternal; +import android.os.Process; +import android.os.RemoteException; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.util.FakeLatencyTracker; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +@RunWith(JUnit4.class) +public class SoundTriggerMiddlewareLoggingTest { + private FakeLatencyTracker mLatencyTracker; + @Mock + private BatteryStatsInternal mBatteryStatsInternal; + @Mock + private ISoundTriggerMiddlewareInternal mDelegateMiddleware; + @Mock + private ISoundTriggerCallback mISoundTriggerCallback; + @Mock + private ISoundTriggerModule mSoundTriggerModule; + private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.READ_DEVICE_CONFIG); + + Identity identity = new Identity(); + identity.uid = Process.myUid(); + identity.pid = Process.myPid(); + identity.packageName = ActivityThread.currentOpPackageName(); + IdentityContext.create(identity); + + mLatencyTracker = FakeLatencyTracker.create(); + mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1); + mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker, + () -> mBatteryStatsInternal, + mDelegateMiddleware); + } + + @After + public void tearDown() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void testSetUpAndTearDown() { + } + + @Test + public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger() + throws RemoteException { + ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( + ISoundTriggerCallback.class); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + + triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), + RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */); + + assertThat(mLatencyTracker.getActiveActionStartTime( + ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1); + } + + @Test + public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException { + ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( + ISoundTriggerCallback.class); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + + triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), + RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */); + long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime( + ACTION_SHOW_VOICE_INTERACTION); + triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), + RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */); + assertThat(mLatencyTracker.getActiveActionStartTime( + ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1); + assertThat(mLatencyTracker.getActiveActionStartTime( + ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime); + } + + @Test + public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent() + throws RemoteException { + ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( + ISoundTriggerCallback.class); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + + triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), + RecognitionStatus.ABORTED, Optional.of(100) /* keyphraseId */); + + assertThat( + mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo( + -1); + } + + @Test + public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId() + throws RemoteException { + ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass( + ISoundTriggerCallback.class); + mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback); + verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture()); + + triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(), + RecognitionStatus.SUCCESS, Optional.empty() /* keyphraseId */); + + assertThat( + mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo( + -1); + } + + private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback, + @RecognitionStatus int triggerEventStatus, Optional<Integer> optionalKeyphraseId) + throws RemoteException { + // trigger a phrase recognition to start a latency tracker session + PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent(); + successEventWithKeyphraseId.common = new RecognitionEvent(); + successEventWithKeyphraseId.common.status = triggerEventStatus; + if (optionalKeyphraseId.isPresent()) { + PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra(); + recognitionExtra.id = optionalKeyphraseId.get(); + successEventWithKeyphraseId.phraseExtras = + new PhraseRecognitionExtra[]{recognitionExtra}; + } + callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId, + 0 /* captureSession */); + } +} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index 7d5750e49907..2f8d17d77e52 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -26,6 +26,7 @@ import android.media.soundtrigger.PhraseRecognitionEvent; import android.media.soundtrigger.PhraseSoundModel; import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.RecognitionEvent; +import android.media.soundtrigger.RecognitionStatus; import android.media.soundtrigger.SoundModel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -36,6 +37,8 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.LatencyTracker; import com.android.server.LocalServices; @@ -44,6 +47,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.Objects; +import java.util.function.Supplier; /** * An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and @@ -71,12 +75,23 @@ import java.util.Objects; public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable { private static final String TAG = "SoundTriggerMiddlewareLogging"; private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; - private final @NonNull Context mContext; + private final @NonNull LatencyTracker mLatencyTracker; + private final @NonNull Supplier<BatteryStatsInternal> mBatteryStatsInternalSupplier; public SoundTriggerMiddlewareLogging(@NonNull Context context, @NonNull ISoundTriggerMiddlewareInternal delegate) { + this(LatencyTracker.getInstance(context), + () -> BatteryStatsHolder.INSTANCE, + delegate); + } + + @VisibleForTesting + public SoundTriggerMiddlewareLogging(@NonNull LatencyTracker latencyTracker, + @NonNull Supplier<BatteryStatsInternal> batteryStatsInternalSupplier, + @NonNull ISoundTriggerMiddlewareInternal delegate) { mDelegate = delegate; - mContext = context; + mLatencyTracker = latencyTracker; + mBatteryStatsInternalSupplier = batteryStatsInternalSupplier; } @Override @@ -294,7 +309,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession) throws RemoteException { try { - BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger( + mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger( SystemClock.elapsedRealtime(), mOriginatorIdentity.uid); mCallbackDelegate.onRecognition(modelHandle, event, captureSession); logVoidReturn("onRecognition", modelHandle, event); @@ -309,7 +324,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt int captureSession) throws RemoteException { try { - BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger( + mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger( SystemClock.elapsedRealtime(), mOriginatorIdentity.uid); startKeyphraseEventLatencyTracking(event); mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession); @@ -361,26 +376,6 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args); } - /** - * Starts the latency tracking log for keyphrase hotword invocation. - * The measurement covers from when the SoundTrigger HAL emits an event to when the - * {@link android.service.voice.VoiceInteractionSession} system UI view is shown. - */ - private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) { - String latencyTrackerTag = null; - if (event.phraseExtras.length > 0) { - latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id; - } - LatencyTracker latencyTracker = LatencyTracker.getInstance(mContext); - // To avoid adding cancel to all of the different failure modes between here and - // showing the system UI, we defensively cancel once. - // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel - // here if any error occurs. - latencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION); - latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION, - latencyTrackerTag); - } - @Override public IBinder asBinder() { return mCallbackDelegate.asBinder(); @@ -399,6 +394,29 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt LocalServices.getService(BatteryStatsInternal.class); } + /** + * Starts the latency tracking log for keyphrase hotword invocation. + * The measurement covers from when the SoundTrigger HAL emits an event to when the + * {@link android.service.voice.VoiceInteractionSession} system UI view is shown. + * + * <p>The session is only started if the {@link PhraseRecognitionEvent} has a status of + * {@link RecognitionStatus#SUCCESS} + */ + private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) { + if (event.common.status != RecognitionStatus.SUCCESS + || ArrayUtils.isEmpty(event.phraseExtras)) { + return; + } + + String latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id; + // To avoid adding cancel to all the different failure modes between here and + // showing the system UI, we defensively cancel once. + // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel + // here if any error occurs. + mLatencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION); + mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION, + latencyTrackerTag); + } //////////////////////////////////////////////////////////////////////////////////////////////// // Actual logging logic below. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java index c35d90f4a495..f7b66a26ff68 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -34,10 +34,13 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENT import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; +import android.content.Context; import android.service.voice.HotwordDetector; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.LatencyTracker; /** * A utility class for logging hotword statistics event. @@ -116,6 +119,46 @@ public final class HotwordMetricsLogger { metricsDetectorType, event, uid, streamSizeBytes, bundleSizeBytes, streamCount); } + /** + * Starts a {@link LatencyTracker} log for the time it takes to show the + * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger. + * + * @see LatencyTracker + * + * @param tag Extra tag to separate different sessions from each other. + */ + public static void startHotwordTriggerToUiLatencySession(Context context, String tag) { + LatencyTracker.getInstance(context).onActionStart(ACTION_SHOW_VOICE_INTERACTION, tag); + } + + /** + * Completes a {@link LatencyTracker} log for the time it takes to show the + * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger. + * + * <p>Completing this session will result in logging metric data.</p> + * + * @see LatencyTracker + */ + public static void stopHotwordTriggerToUiLatencySession(Context context) { + LatencyTracker.getInstance(context).onActionEnd(ACTION_SHOW_VOICE_INTERACTION); + } + + /** + * Cancels a {@link LatencyTracker} log for the time it takes to show the + * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger. + * + * <p>Cancels typically occur when the VoiceInteraction session UI is shown for reasons outside + * of a {@link android.hardware.soundtrigger.SoundTrigger.RecognitionEvent} such as an + * invocation from an external source or service.</p> + * + * <p>Canceling this session will not result in logging metric data. + * + * @see LatencyTracker + */ + public static void cancelHotwordTriggerToUiLatencySession(Context context) { + LatencyTracker.getInstance(context).onActionCancel(ACTION_SHOW_VOICE_INTERACTION); + } + private static int getCreateMetricsDetectorType(int detectorType) { switch (detectorType) { case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 1a76295c251f..e1da2ca2a086 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -97,7 +97,6 @@ import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; -import com.android.internal.util.LatencyTracker; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -443,6 +442,10 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { + // HotwordDetector trigger uses VoiceInteractionService#showSession + // We need to cancel here because UI is not being shown due to a SoundTrigger + // HAL event. + HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext); mImpl.showSessionLocked(options, VoiceInteractionSession.SHOW_SOURCE_ACTIVITY, attributionTag, new IVoiceInteractionSessionShowCallback.Stub() { @@ -994,6 +997,13 @@ public class VoiceInteractionManagerService extends SystemService { Slog.w(TAG, "showSessionFromSession without running voice interaction service"); return false; } + // If the token is null, then the request to show the session is not coming from + // the active VoiceInteractionService session. + // We need to cancel here because UI is not being shown due to a SoundTrigger + // HAL event. + if (token == null) { + HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext); + } final long caller = Binder.clearCallingIdentity(); try { return mImpl.showSessionLocked(sessionArgs, flags, attributionTag, null, null); @@ -1862,6 +1872,11 @@ public class VoiceInteractionManagerService extends SystemService { final long caller = Binder.clearCallingIdentity(); try { + // HotwordDetector trigger uses VoiceInteractionService#showSession + // We need to cancel here because UI is not being shown due to a SoundTrigger + // HAL event. + HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext); + return mImpl.showSessionLocked(args, sourceFlags | VoiceInteractionSession.SHOW_WITH_ASSIST @@ -2521,8 +2536,11 @@ public class VoiceInteractionManagerService extends SystemService { public void onVoiceSessionWindowVisibilityChanged(boolean visible) throws RemoteException { if (visible) { - LatencyTracker.getInstance(mContext) - .onActionEnd(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION); + // The AlwaysOnHotwordDetector trigger latency is always completed here even + // if the reason the window was shown was not due to a SoundTrigger HAL + // event. It is expected that the latency will be canceled if shown for + // other invocation reasons, and this call becomes a noop. + HotwordMetricsLogger.stopHotwordTriggerToUiLatencySession(mContext); } } diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp index e5eb3c7b6394..7af76e1144f8 100644 --- a/tests/componentalias/Android.bp +++ b/tests/componentalias/Android.bp @@ -16,6 +16,9 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +// TODO: Delete this file. It's no longer needed, but removing it on udc-dev will cause +// a conflict on master. + java_defaults { name: "ComponentAliasTests_defaults", static_libs: [ @@ -34,54 +37,3 @@ java_defaults { ], platform_apis: true, // We use hidden APIs in the test. } - -// We build three APKs from the exact same source files, so these APKs contain the exact same tests. -// And we run the tests on each APK, so that we can test various situations: -// - When the alias is in the same package, target in the same package. -// - When the alias is in the same package, target in another package. -// - When the alias is in another package, which also contains the target. -// - When the alias is in another package, and the target is in yet another package. -// etc etc... - -android_test { - name: "ComponentAliasTests", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_main.xml", - "AndroidManifest_service_aliases.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} - -android_test { - name: "ComponentAliasTests1", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests.sub1", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_sub1.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} - -android_test { - name: "ComponentAliasTests2", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests.sub2", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_sub2.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml deleted file mode 100755 index 7bb83a336833..000000000000 --- a/tests/componentalias/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - <uses-library android:name="android.test.runner" /> - <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" /> - </application> -</manifest> diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml deleted file mode 100755 index 70e817ebf3e7..000000000000 --- a/tests/componentalias/AndroidManifest_main.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidManifest_service_aliases.xml b/tests/componentalias/AndroidManifest_service_aliases.xml deleted file mode 100644 index c96f1736c684..000000000000 --- a/tests/componentalias/AndroidManifest_service_aliases.xml +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - <application> - <!-- - Note the alias components are essentially just placeholders, so the APKs don't have to - have the implementation classes. - --> - <service android:name=".s.Alias00" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.s.Target00" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter> - </service> - <service android:name=".s.Alias01" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target01" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter> - </service> - <service android:name=".s.Alias02" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target02" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter> - </service> - <service android:name=".s.Alias03" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target03" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter> - </service> - <service android:name=".s.Alias04" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target04" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter> - </service> - - <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/componentalias/AndroidManifest_service_targets.xml b/tests/componentalias/AndroidManifest_service_targets.xml deleted file mode 100644 index 24c0432bcf4c..000000000000 --- a/tests/componentalias/AndroidManifest_service_targets.xml +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - <application> - <service android:name=".s.Target00" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target01" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target02" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target03" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target04" android:exported="true" android:enabled="true" > - </service> - - <!-- - Due to http://go/intents-match-intent-filters-guide, the target intent has to have - an intent filter that matches the original intent. (modulo the package name) - This restriction shouldn't exist in the final version. - --> - <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml deleted file mode 100755 index 21616f5edf00..000000000000 --- a/tests/componentalias/AndroidManifest_sub1.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests.sub1" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml deleted file mode 100755 index c11b0cd55ef4..000000000000 --- a/tests/componentalias/AndroidManifest_sub2.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests.sub2" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml deleted file mode 100644 index afdfe79ea4a4..000000000000 --- a/tests/componentalias/AndroidTest-template.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<configuration> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="ComponentAliasTests.apk" /> - <option name="test-file-name" value="ComponentAliasTests1.apk" /> - <option name="test-file-name" value="ComponentAliasTests2.apk" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. --> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" /> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" /> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub2" /> - - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests" /> - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub1" /> - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub2" /> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="{PACKAGE}" /> - <option name="runtime-hint" value="2m" /> - <option name="isolated-storage" value="false" /> - </test> -</configuration> diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java deleted file mode 100644 index 99322ee46106..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -import android.content.ComponentName; -import android.content.Context; -import android.os.Build; -import android.provider.DeviceConfig; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Before; - -import java.util.function.Consumer; - -public class BaseComponentAliasTest { - protected static final Context sContext = InstrumentationRegistry.getTargetContext(); - - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - @Before - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeTrue(Build.isDebuggable()); - ShellUtils.runShellCommand( - "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.set("enable_experimental_component_alias", ""); - sDeviceConfig.set("component_alias_overrides", ""); - - // Make sure the feature is actually enabled, and the aliases are loaded. - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - String out = ShellUtils.runShellCommand("dumpsys activity component-alias"); - - return out.contains("Enabled: true") - && out.contains("android.content.componentalias.tests/.b.Alias04") - && out.contains("android.content.componentalias.tests/.s.Alias04"); - }); - ShellUtils.runShellCommand("am wait-for-broadcast-idle"); - } - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - ShellUtils.runShellCommand( - "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.close(); - } - - protected static void log(String message) { - Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message); - } - - /** - * Defines a test target. - */ - public static class Combo { - public final ComponentName alias; - public final ComponentName target; - public final String action; - - public Combo(ComponentName alias, ComponentName target, String action) { - this.alias = alias; - this.target = target; - this.action = action; - } - - @Override - public String toString() { - return "Combo{" - + "alias=" + toString(alias) - + ", target=" + toString(target) - + ", action='" + action + '\'' - + '}'; - } - - private static String toString(ComponentName cn) { - return cn == null ? "[null]" : cn.flattenToShortString(); - } - - public void apply(Consumer<Combo> callback) { - log("Testing for: " + this); - callback.accept(this); - } - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java deleted file mode 100644 index 7d5e0b9c6d8a..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.ComponentName; -import android.content.Intent; - -import com.android.compatibility.common.util.BroadcastMessenger.Receiver; - -import org.junit.Test; - -import java.util.function.Consumer; - -public class ComponentAliasBroadcastTest extends BaseComponentAliasTest { - private void forEachCombo(Consumer<Combo> callback) { - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias00"), - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Target00"), - MAIN_PACKAGE + ".IS_RECEIVER_00").apply(callback); - - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias01"), - new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".b.Target01"), - MAIN_PACKAGE + ".IS_RECEIVER_01").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias02"), - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".b.Target02"), - MAIN_PACKAGE + ".IS_RECEIVER_02").apply(callback); - } - - @Test - public void testBroadcast_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - i.setAction("ACTION_BROADCAST"); - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - log("Sending: " + i); - sContext.sendBroadcast(i); - - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - - // The broadcast intent will always have the receiving component name set. - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - - receiver.ensureNoMoreMessages(); - } - }); - } - - @Test - public void testBroadcast_explicitPackageName() { - forEachCombo((c) -> { - // In this test, we only set the package name to the intent. - // If the alias and target are the same package, the intent will be sent to both of them - // *and* the one to the alias is redirected to the target, so the target will receive - // the intent twice. This case is haled at *1 below. - - - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - log("Sending broadcast: " + i); - sContext.sendBroadcast(i); - - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - - // *1 -- if the alias and target are in the same package, we expect one more - // message. - if (c.alias.getPackageName().equals(c.target.getPackageName())) { - m = receiver.waitForNextMessage(); - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - } - receiver.ensureNoMoreMessages(); - } - }); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java deleted file mode 100644 index ee20379d971a..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -import android.os.Build; -import android.provider.DeviceConfig; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Test; - -public class ComponentAliasEnableWithDeviceConfigTest { - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - sDeviceConfig.close(); - } - - @Test - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeTrue(Build.isDebuggable()); - - sDeviceConfig.set("component_alias_overrides", ""); - - // First, disable with both compat-id and device config. - ShellUtils.runShellCommand( - "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.set("enable_experimental_component_alias", ""); - - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: false") > 0; - }); - - // Then, enable by device config. - sDeviceConfig.set("enable_experimental_component_alias", "true"); - - // Make sure the feature is actually enabled. - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: true") > 0; - }); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java deleted file mode 100644 index d41696f27880..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Intent; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.internal.util.DataClass; - -/** - * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger. - * - * To add a new field, just add a private member field, and run: - * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java - */ -@DataClass( - genConstructor = false, - genSetters = true, - genToString = true, - genAidl = false) -public final class ComponentAliasMessage implements Parcelable { - public ComponentAliasMessage() { - } - - @Nullable - private String mMessage; - - @Nullable - private String mMethodName; - - @Nullable - private String mSenderIdentity; - - @Nullable - private Intent mIntent; - - @Nullable - private ComponentName mComponent; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - public @Nullable String getMessage() { - return mMessage; - } - - @DataClass.Generated.Member - public @Nullable String getMethodName() { - return mMethodName; - } - - @DataClass.Generated.Member - public @Nullable String getSenderIdentity() { - return mSenderIdentity; - } - - @DataClass.Generated.Member - public @Nullable Intent getIntent() { - return mIntent; - } - - @DataClass.Generated.Member - public @Nullable ComponentName getComponent() { - return mComponent; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setMessage(@NonNull String value) { - mMessage = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) { - mMethodName = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) { - mSenderIdentity = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) { - mIntent = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) { - mComponent = value; - return this; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "ComponentAliasMessage { " + - "message = " + mMessage + ", " + - "methodName = " + mMethodName + ", " + - "senderIdentity = " + mSenderIdentity + ", " + - "intent = " + mIntent + ", " + - "component = " + mComponent + - " }"; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - byte flg = 0; - if (mMessage != null) flg |= 0x1; - if (mMethodName != null) flg |= 0x2; - if (mSenderIdentity != null) flg |= 0x4; - if (mIntent != null) flg |= 0x8; - if (mComponent != null) flg |= 0x10; - dest.writeByte(flg); - if (mMessage != null) dest.writeString(mMessage); - if (mMethodName != null) dest.writeString(mMethodName); - if (mSenderIdentity != null) dest.writeString(mSenderIdentity); - if (mIntent != null) dest.writeTypedObject(mIntent, flags); - if (mComponent != null) dest.writeTypedObject(mComponent, flags); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ ComponentAliasMessage(@NonNull Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - byte flg = in.readByte(); - String message = (flg & 0x1) == 0 ? null : in.readString(); - String methodName = (flg & 0x2) == 0 ? null : in.readString(); - String senderIdentity = (flg & 0x4) == 0 ? null : in.readString(); - Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR); - ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR); - - this.mMessage = message; - this.mMethodName = methodName; - this.mSenderIdentity = senderIdentity; - this.mIntent = intent; - this.mComponent = component; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR - = new Parcelable.Creator<ComponentAliasMessage>() { - @Override - public ComponentAliasMessage[] newArray(int size) { - return new ComponentAliasMessage[size]; - } - - @Override - public ComponentAliasMessage createFromParcel(@NonNull Parcel in) { - return new ComponentAliasMessage(in); - } - }; - - @DataClass.Generated( - time = 1630098801203L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java", - inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java deleted file mode 100644 index 0899886fe951..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Build; -import android.provider.DeviceConfig; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Test; - -/** - * Test to make sure component-alias can't be enabled on user builds. - */ -public class ComponentAliasNotSupportedOnUserBuildTest { - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - sDeviceConfig.close(); - } - - @Test - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeFalse(Build.isDebuggable()); - - // Try to enable it by both the device config and compat-id. - sDeviceConfig.set("enable_experimental_component_alias", "true"); - ShellUtils.runShellCommand( - "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - - // Sleep for an arbitrary amount of time, so the config would sink in, if there was - // no "not on user builds" check. - - Thread.sleep(5000); - - // Make sure the feature is still disabled. - assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: false") > 0).isTrue(); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java deleted file mode 100644 index f0ff088815af..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.componentalias.tests; - -import static android.content.Context.BIND_AUTO_CREATE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import static com.google.common.truth.Truth.assertThat; - -import static org.hamcrest.core.IsNot.not; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; - -import com.android.compatibility.common.util.BroadcastMessenger; -import com.android.compatibility.common.util.BroadcastMessenger.Receiver; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.Assume; -import org.junit.Test; - -import java.util.function.Consumer; - -/** - * Test for the experimental "Component alias" feature. - * - * Note this test exercises the relevant APIs, but don't actually check if the aliases are - * resolved. - * - * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run - * BG services. - */ -public class ComponentAliasServiceTest extends BaseComponentAliasTest { - /** - * Service connection used throughout the tests. It sends a message for each callback via - * the messenger. - */ - private static final ServiceConnection sServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - log("onServiceConnected: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onServiceConnected") - .setComponent(name); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - log("onServiceDisconnected: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onServiceDisconnected") - .setComponent(name); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onBindingDied(ComponentName name) { - log("onBindingDied: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onBindingDied"); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onNullBinding(ComponentName name) { - log("onNullBinding: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onNullBinding"); - - BroadcastMessenger.send(sContext, TAG, m); - } - }; - - private void testStartAndStopService_common( - Intent originalIntent, - ComponentName componentNameForClient, - ComponentName componentNameForTarget) { - - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Start the service. - ComponentName result = sContext.startService(originalIntent); - assertThat(result).isEqualTo(componentNameForClient); - - // Check - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onStartCommand"); - // The app sees the rewritten intent. - assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); - - // Verify the original intent. - assertThat(m.getIntent().getOriginalIntent().getComponent()) - .isEqualTo(originalIntent.getComponent()); - assertThat(m.getIntent().getOriginalIntent().getPackage()) - .isEqualTo(originalIntent.getPackage()); - - // Stop the service. - sContext.stopService(originalIntent); - - // Check - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onDestroy"); - - receiver.ensureNoMoreMessages(); - } - } - - private void forEachCombo(Consumer<Combo> callback) { - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias00"), - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target00"), - MAIN_PACKAGE + ".IS_ALIAS_00").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"), - new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".s.Target01"), - MAIN_PACKAGE + ".IS_ALIAS_01").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"), - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"), - MAIN_PACKAGE + ".IS_ALIAS_02").apply(callback); - } - - @Test - public void testStartAndStopService_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - testStartAndStopService_common(i, c.alias, c.target); - }); - } - - @Test - public void testStartAndStopService_explicitPackageName() { - forEachCombo((c) -> { - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - - testStartAndStopService_common(i, c.alias, c.target); - }); - } - - @Test - public void testStartAndStopService_override() throws Exception { - Intent i = new Intent().setPackage(MAIN_PACKAGE); - i.setAction(MAIN_PACKAGE + ".IS_ALIAS_01"); - - // Change some of the aliases from what's defined in <meta-data>. - - ComponentName aliasA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"); - ComponentName targetA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target02"); - - ComponentName aliasB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); - ComponentName targetB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target01"); - - sDeviceConfig.set("component_alias_overrides", - aliasA.flattenToShortString() + ":" + targetA.flattenToShortString() - + "," - + aliasB.flattenToShortString() + ":" + targetB.flattenToShortString()); - - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf(aliasA.flattenToShortString() - + " -> " + targetA.flattenToShortString()) > 0; - }); - - - testStartAndStopService_common(i, aliasA, targetA); - } - - private void testBindAndUnbindService_common( - Intent originalIntent, - ComponentName componentNameForClient, - ComponentName componentNameForTarget) { - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Bind to the service. - assertThat(sContext.bindService( - originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onBind"); - // The app sees the rewritten intent. - assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); - - // Verify the original intent. - assertThat(m.getIntent().getOriginalIntent().getComponent()) - .isEqualTo(originalIntent.getComponent()); - assertThat(m.getIntent().getOriginalIntent().getPackage()) - .isEqualTo(originalIntent.getPackage()); - - // Check the client side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); - // The app sees the rewritten intent. - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - - // Unbind. - sContext.unbindService(sServiceConnection); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onDestroy"); - - // Note onServiceDisconnected() won't be called in this case. - receiver.ensureNoMoreMessages(); - } - } - - @Test - public void testBindService_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - - testBindAndUnbindService_common(i, c.alias, c.target); - }); - - } - - @Test - public void testBindService_explicitPackageName() { - forEachCombo((c) -> { - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - - testBindAndUnbindService_common(i, c.alias, c.target); - }); - } - - /** - * Make sure, when the service process is killed, the client will get a callback with the - * right component name. - */ - @Test - public void testBindService_serviceKilled() { - - // We need to kill SUB2_PACKAGE, don't run it for this package. - Assume.assumeThat(sContext.getPackageName(), not(SUB2_PACKAGE)); - - Intent originalIntent = new Intent().setPackage(MAIN_PACKAGE); - originalIntent.setAction(MAIN_PACKAGE + ".IS_ALIAS_02"); - - final ComponentName componentNameForClient = - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); - final ComponentName componentNameForTarget = - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"); - - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Bind to the service. - assertThat(sContext.bindService( - originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onBind"); - - m = receiver.waitForNextMessage(); - assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - // We don't need to check all the fields because these are tested else where. - - // Now kill the service process. - ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected"); - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - - receiver.ensureNoMoreMessages(); - } - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java deleted file mode 100644 index 165d728c92a6..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests; - -public final class ComponentAliasTestCommon { - private ComponentAliasTestCommon() { - } - - public static final String TAG = "ComponentAliasTest"; - - public static final String MAIN_PACKAGE = "android.content.componentalias.tests"; - - public static final String SUB1_PACKAGE = "android.content.componentalias.tests.sub1"; - public static final String SUB2_PACKAGE = "android.content.componentalias.tests.sub2"; -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java deleted file mode 100644 index 1d05e72a2f3f..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.b; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.componentalias.tests.ComponentAliasMessage; -import android.util.Log; - -import com.android.compatibility.common.util.BroadcastMessenger; - -public class BaseReceiver extends BroadcastReceiver { - private String getMyIdentity(Context context) { - return (new ComponentName(context.getPackageName(), this.getClass().getCanonicalName())) - .flattenToShortString(); - } - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "onReceive: on " + getMyIdentity(context) + " intent=" + intent); - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity(context)) - .setMethodName("onReceive") - .setIntent(intent); - BroadcastMessenger.send(context, TAG, m); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java deleted file mode 100644 index 06f7a13f73d7..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.b; - -public class Target01 extends BaseReceiver { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java deleted file mode 100644 index df7579d8304d..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.b; - -public class Target02 extends BaseReceiver { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java deleted file mode 100644 index 5ae55215f696..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.b; - -public class Target03 extends BaseReceiver { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java deleted file mode 100644 index f9b9886b0bb2..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.b; - -public class Target04 extends BaseReceiver { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java deleted file mode 100644 index 535d9b80f100..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import android.app.Service; -import android.content.ComponentName; -import android.content.Intent; -import android.content.componentalias.tests.ComponentAliasMessage; -import android.os.Binder; -import android.os.IBinder; -import android.util.Log; - -import com.android.compatibility.common.util.BroadcastMessenger; - -public class BaseService extends Service { - private String getMyIdentity() { - return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName())) - .flattenToShortString(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent); - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onStartCommand") - .setIntent(intent); - BroadcastMessenger.send(this, TAG, m); - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - Log.i(TAG, "onDestroy: on " + getMyIdentity()); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onDestroy"); - BroadcastMessenger.send(this, TAG, m); - } - - @Override - public IBinder onBind(Intent intent) { - Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onBind") - .setIntent(intent); - BroadcastMessenger.send(this, TAG, m); - - return new Binder(); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java deleted file mode 100644 index 64b91f5695f5..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -public class Target00 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java deleted file mode 100644 index bd589991d7dc..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -public class Target01 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java deleted file mode 100644 index 0ddf8188768b..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -public class Target02 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java deleted file mode 100644 index 0dbc0501b6f9..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -public class Target03 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java deleted file mode 100644 index 099425867f02..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.content.componentalias.tests.s; - -public class Target04 extends BaseService { -} diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp index 7375c160a59f..43f21221ae5a 100644 --- a/tools/lint/fix/Android.bp +++ b/tools/lint/fix/Android.bp @@ -23,9 +23,8 @@ package { python_binary_host { name: "lint_fix", - main: "lint_fix.py", - srcs: ["lint_fix.py"], - libs: ["soong_lint_fix"], + main: "soong_lint_fix.py", + srcs: ["soong_lint_fix.py"], } python_library_host { diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md index 367d0bcb1aa7..a5ac2be1c18a 100644 --- a/tools/lint/fix/README.md +++ b/tools/lint/fix/README.md @@ -5,9 +5,12 @@ Inspiration: go/refactor-the-platform-with-lint\ ## What is this? It's a python script that runs the framework linter, -and then copies modified files back into the source tree.\ +and then (optionally) copies modified files back into the source tree.\ Why python, you ask? Because python is cool ¯\_(ツ)_/¯. +Incidentally, this exposes a much simpler way to run individual lint checks +against individual modules, so it's useful beyond applying fixes. + ## Why? Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag. @@ -17,30 +20,11 @@ directory. This script runs the lint, unpacks those files, and copies them back ## How do I run it? **WARNING: You probably want to commit/stash any changes to your working tree before doing this...** -From this directory, run `python lint_fix.py -h`. -The script's help output explains things that are omitted here. - -Alternatively, there is a python binary target you can build to make this -available anywhere in your tree: ``` +source build/envsetup.sh +lunch cf_x86_64_phone-userdebug # or any lunch target m lint_fix lint_fix -h ``` -**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. - -Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run` -```shell -( -export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation; -cd $ANDROID_BUILD_TOP; -source build/envsetup.sh; -rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; -m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html; -cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint; -unzip suggested-fixes.zip -d suggested-fixes; -cd suggested-fixes; -find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --; -rm -rf suggested-fixes -) -``` +The script's help output explains things that are omitted here. diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py deleted file mode 100644 index 1c83f7b38400..000000000000 --- a/tools/lint/fix/lint_fix.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from soong_lint_fix import SoongLintFix - -SoongLintFix().run() diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py index 3308df6fc5af..cd4d778d1dec 100644 --- a/tools/lint/fix/soong_lint_fix.py +++ b/tools/lint/fix/soong_lint_fix.py @@ -13,14 +13,21 @@ # limitations under the License. import argparse +import json import os +import shutil import subprocess import sys +import zipfile ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") +ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT") +PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/") + +SOONG_UI = "build/soong/soong_ui.bash" PATH_PREFIX = "out/soong/.intermediates" PATH_SUFFIX = "android_common/lint" -FIX_DIR = "suggested-fixes" +FIX_ZIP = "suggested-fixes.zip" class SoongLintFix: """ @@ -28,14 +35,12 @@ class SoongLintFix: apply lint fixes to the platform via the necessary combination of soong and shell commands. - It provides some basic hooks for experimental code - to tweak the generation of the resulting shell script. - - By default, it will apply lint fixes using the intermediate `suggested-fixes` - directory that soong creates during its invocation of lint. + It breaks up these operations into a few "private" methods + that are intentionally exposed so experimental code can tweak behavior. - The default argument parser configures a number of command line arguments to - facilitate running lint via soong. + The entry point, `run`, will apply lint fixes using the + intermediate `suggested-fixes` directory that soong creates during its + invocation of lint. Basic usage: ``` @@ -45,99 +50,95 @@ class SoongLintFix: ``` """ def __init__(self): - self._commands = None + self._parser = _setup_parser() self._args = None + self._kwargs = None self._path = None self._target = None - self._parser = _setup_parser() - - - def add_argument(self, *args, **kwargs): - """ - If necessary, add arguments to the underlying argparse.ArgumentParser before running - """ - self._parser.add_argument(*args, **kwargs) - def run(self, add_setup_commands=None, override_fix_commands=None): + def run(self, additional_setup=None, custom_fix=None): """ Run the script - :param add_setup_commands: OPTIONAL function to add additional setup commands - passed the command line arguments, path, and build target - must return a list of strings (the additional commands) - :param override_fix_commands: OPTIONAL function to override the fix commands - passed the command line arguments, path, and build target - must return a list of strings (the fix commands) """ self._setup() - if add_setup_commands: - self._commands += add_setup_commands(self._args, self._path, self._target) - - self._add_lint_report_commands() + self._find_module() + self._lint() if not self._args.no_fix: - if override_fix_commands: - self._commands += override_fix_commands(self._args, self._path, self._target) - else: - self._commands += [ - f"cd {self._path}", - f"unzip {FIX_DIR}.zip -d {FIX_DIR}", - f"cd {FIX_DIR}", - # Find all the java files in the fix directory, excluding the ./out subdirectory, - # and copy them back into the same path within the tree. - f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1 || exit 255' --", - f"rm -rf {FIX_DIR}" - ] - - - if self._args.dry_run: - print(self._get_commands_str()) - else: - self._execute() + self._fix() + if self._args.print: + self._print() def _setup(self): self._args = self._parser.parse_args() - self._commands = [] - self._path = f"{PATH_PREFIX}/{self._args.build_path}/{PATH_SUFFIX}" - self._target = f"{self._path}/lint-report.html" + env = os.environ.copy() + if self._args.check: + env["ANDROID_LINT_CHECK"] = self._args.check + if self._args.lint_module: + env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module - if not self._args.dry_run: - self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"] + self._kwargs = { + "env": env, + "executable": "/bin/bash", + "shell": True, + } - if self._args.check: - self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"] + os.chdir(ANDROID_BUILD_TOP) + + + def _find_module(self): + print("Refreshing soong modules...") + try: + os.mkdir(ANDROID_PRODUCT_OUT) + except OSError: + pass + subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs) + print("done.") + + with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f: + module_info = json.load(f) + + if self._args.module not in module_info: + sys.exit(f"Module {self._args.module} not found!") + module_path = module_info[self._args.module]["path"][0] + print(f"Found module {module_path}/{self._args.module}.") - def _add_lint_report_commands(self): - self._commands += [ - "cd $ANDROID_BUILD_TOP", - "source build/envsetup.sh", - # remove the file first so soong doesn't think there is no work to do - f"rm {self._target}", - # remove in case there are fixes from a prior run, - # that we don't want applied if this run fails - f"rm {self._path}/{FIX_DIR}.zip", - f"m {self._target}", - ] + self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}" + self._target = f"{self._path}/lint-report.txt" - def _get_commands_str(self): - prefix = "(\n" - delimiter = ";\n" - suffix = "\n)" - return f"{prefix}{delimiter.join(self._commands)}{suffix}" + def _lint(self): + print("Cleaning up any old lint results...") + try: + os.remove(f"{self._target}") + os.remove(f"{self._path}/{FIX_ZIP}") + except FileNotFoundError: + pass + print("done.") + print(f"Generating {self._target}") + subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs) + print("done.") - def _execute(self, with_echo=True): - if with_echo: - exec_commands = [] - for c in self._commands: - exec_commands.append(f'echo "{c}"') - exec_commands.append(c) - self._commands = exec_commands - subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True) + def _fix(self): + print("Copying suggested fixes to the tree...") + with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip: + for name in zip.namelist(): + if name.startswith("out") or not name.endswith(".java"): + continue + with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst: + shutil.copyfileobj(src, dst) + print("done.") + + + def _print(self): + print("### lint-report.txt ###", end="\n\n") + with open(self._target, "r") as f: + print(f.read()) def _setup_parser(): @@ -147,23 +148,26 @@ def _setup_parser(): 2. Run lint on the specified target. 3. Copy the modified files, from soong's intermediate directory, back into the tree. - **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` - so that the `ANDROID_BUILD_TOP` environment variable has been set. - Alternatively, set it manually in your shell. + **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. """, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('build_path', metavar='build_path', type=str, - help='The build module to run ' - '(e.g. frameworks/base/framework-minus-apex or ' - 'frameworks/base/services/core/services.core.unboosted)') + parser.add_argument('module', + help='The soong build module to run ' + '(e.g. framework-minus-apex or services.core.unboosted)') - parser.add_argument('--check', metavar='check', type=str, + parser.add_argument('--check', help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') - parser.add_argument('--dry-run', dest='dry_run', action='store_true', - help='Just print the resulting shell script instead of running it.') + parser.add_argument('--lint-module', + help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.') - parser.add_argument('--no-fix', dest='no_fix', action='store_true', + parser.add_argument('--no-fix', action='store_true', help='Just build and run the lint, do NOT apply the fixes.') + parser.add_argument('--print', action='store_true', + help='Print the contents of the generated lint-report.txt at the end.') + return parser + +if __name__ == "__main__": + SoongLintFix().run()
\ No newline at end of file diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt index f7560a712f70..75b00737a168 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -321,6 +321,34 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { ) } + fun testDoesDetectIssuesShortStringsNotAllowed() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass121 extends IFooMethod.Stub { + @Override + @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass121.java:6: Error: The method \ + TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \ + which differs from the overridden method Stub.testMethodAnyLiteral: \ + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAnyLiteral() {} + ~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + /* Stubs */ // A service with permission annotation on the method. |