summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java29
-rw-r--r--core/java/android/animation/Animator.java143
-rw-r--r--core/java/android/animation/AnimatorSet.java96
-rw-r--r--core/java/android/animation/ValueAnimator.java76
-rw-r--r--core/java/android/app/ActivityManager.java9
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageManager.java20
-rw-r--r--core/java/android/os/PermissionEnforcer.java73
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java50
-rw-r--r--core/java/android/util/FeatureFlagUtils.java4
-rw-r--r--core/java/android/view/SurfaceControl.java6
-rw-r--r--core/java/android/view/SurfaceView.java32
-rw-r--r--core/java/android/view/TEST_MAPPING3
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java2
-rw-r--r--core/java/android/view/inputmethod/HandwritingGesture.java13
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java9
-rw-r--r--core/java/android/webkit/WebViewClient.java3
-rw-r--r--core/java/com/android/internal/expresslog/Histogram.java116
-rw-r--r--core/java/com/android/internal/policy/TransitionAnimation.java11
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp4
-rw-r--r--core/res/res/layout/notification_template_material_base.xml3
-rw-r--r--core/res/res/values/dimens.xml2
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java8
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java526
-rw-r--r--core/tests/coretests/src/android/animation/ValueAnimatorTests.java60
-rw-r--r--core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java6
-rw-r--r--core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java167
-rw-r--r--core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java10
-rw-r--r--data/etc/com.android.intentresolver.xml1
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java227
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt9
-rw-r--r--libs/hwui/MemoryPolicy.h1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt32
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java2
-rw-r--r--packages/SystemUI/res/layout/notification_info.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java121
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl9
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt196
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt105
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java14
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt17
-rw-r--r--packages/WallpaperBackup/Android.bp5
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java61
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java139
-rw-r--r--packages/WallpaperBackup/test/AndroidManifest.xml15
-rw-r--r--packages/WallpaperBackup/test/res/xml/livewallpaper.xml17
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java251
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java204
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java (renamed from tests/componentalias/src/android/content/componentalias/tests/b/Target00.java)16
-rw-r--r--services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java108
-rw-r--r--services/core/java/com/android/server/am/ComponentAliasResolver.java36
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java89
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java12
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java12
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java3
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java14
-rw-r--r--services/core/java/com/android/server/pm/AppDataHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java12
-rw-r--r--services/core/java/com/android/server/pm/Installer.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java45
-rw-r--r--services/core/java/com/android/server/pm/PackageVerificationState.java47
-rw-r--r--services/core/java/com/android/server/pm/VerificationUtils.java49
-rw-r--r--services/core/java/com/android/server/pm/dex/DexManager.java57
-rw-r--r--services/core/java/com/android/server/powerstats/BatteryTrigger.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java36
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java14
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java105
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java249
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java101
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java69
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java68
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java6
-rw-r--r--services/tests/voiceinteractiontests/Android.bp1
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java181
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java66
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java43
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java24
-rw-r--r--tests/componentalias/Android.bp54
-rwxr-xr-xtests/componentalias/AndroidManifest.xml25
-rwxr-xr-xtests/componentalias/AndroidManifest_main.xml28
-rw-r--r--tests/componentalias/AndroidManifest_service_aliases.xml81
-rw-r--r--tests/componentalias/AndroidManifest_service_targets.xml57
-rwxr-xr-xtests/componentalias/AndroidManifest_sub1.xml28
-rwxr-xr-xtests/componentalias/AndroidManifest_sub2.xml28
-rw-r--r--tests/componentalias/AndroidTest-template.xml38
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java103
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java110
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java63
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java215
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java60
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java315
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java28
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java44
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/b/Target01.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/b/Target02.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/b/Target03.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/b/Target04.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java70
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/Target00.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/Target01.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/Target02.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/Target03.java19
-rw-r--r--tests/componentalias/src/android/content/componentalias/tests/s/Target04.java19
-rw-r--r--tools/lint/fix/Android.bp5
-rw-r--r--tools/lint/fix/README.md30
-rw-r--r--tools/lint/fix/lint_fix.py29
-rw-r--r--tools/lint/fix/soong_lint_fix.py180
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt28
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.