Merge "Only log bubble overflow max reached when in stack view" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a299408..e184704 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -100,7 +100,7 @@
"com.android.media.flags.performance-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
"com.android.net.thread.platform.flags-aconfig-java",
- "com.android.ranging.flags.ranging-aconfig-java",
+ "com.android.ranging.flags.ranging-aconfig-java-export",
"com.android.server.contextualsearch.flags-java",
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
@@ -373,6 +373,11 @@
name: "android.security.flags-aconfig-java-export",
aconfig_declarations: "android.security.flags-aconfig",
mode: "exported",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.wifi",
+ ],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -1651,13 +1656,6 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// Ranging
-java_aconfig_library {
- name: "com.android.ranging.flags.ranging-aconfig-java",
- aconfig_declarations: "ranging_aconfig_flags",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
// System Server
aconfig_declarations {
name: "android.systemserver.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 45f6a3f..a42a8de 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -24166,6 +24166,8 @@
field public static final String KEY_OPERATING_RATE = "operating-rate";
field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
field public static final String KEY_PCM_ENCODING = "pcm-encoding";
+ field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public static final String KEY_PICTURE_PROFILE_ID = "picture-profile-id";
+ field @FlaggedApi("android.media.tv.flags.apply_picture_profiles") public static final String KEY_PICTURE_PROFILE_INSTANCE = "picture-profile-instance";
field public static final String KEY_PICTURE_TYPE = "picture-type";
field public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
field public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
@@ -25613,6 +25615,7 @@
method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
method public int getImmersiveAudioLevel();
+ method @FlaggedApi("android.media.audio.spatializer_capabilities") @NonNull public java.util.List<java.lang.Integer> getSpatializedChannelMasks();
method public boolean isAvailable();
method public boolean isEnabled();
method public boolean isHeadTrackerAvailable();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e766ae2..42fa9e7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -14453,7 +14453,7 @@
* </ul>
* <p>
* The following methods are supported for the parent instance but can only be called by the
- * profile owner of a managed profile that was created during the device provisioning flow:
+ * profile owner on an <a href="#organization-owned">organization owned</a> managed profile:
* <ul>
* <li>{@link #getPasswordComplexity}</li>
* <li>{@link #setCameraDisabled}</li>
@@ -14461,11 +14461,6 @@
* <li>{@link #setAccountManagementDisabled(ComponentName, String, boolean)}</li>
* <li>{@link #setPermittedInputMethods}</li>
* <li>{@link #getPermittedInputMethods}</li>
- * </ul>
- *
- * <p>The following methods can be called by the profile owner of a managed profile
- * on an organization-owned device:
- * <ul>
* <li>{@link #wipeData}</li>
* </ul>
*
@@ -18177,4 +18172,4 @@
}
return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3e6919b..46da4a3 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -81,14 +81,3 @@
description: "Enable virtual stylus input"
bug: "304829446"
}
-
-flag {
- name: "impulse_velocity_strategy_for_touch_navigation"
- is_exported: true
- namespace: "virtual_devices"
- description: "Use impulse velocity strategy during conversion of touch navigation flings into Dpad events"
- bug: "338426241"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
\ No newline at end of file
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 4c9f08d..036ccd8 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -123,6 +123,11 @@
// We can lift this restriction in the future after we've made it possible for test authors
// to test Looper and MessageQueue without resorting to reflection.
+ // Holdback study.
+ if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
+ mUseConcurrent = false;
+ }
+
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e63b664..94259d7 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -89,6 +89,8 @@
per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
# PerformanceHintManager
+per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS
+per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS
per-file PerformanceHintManager.java = file:/ADPF_OWNERS
per-file WorkDuration.java = file:/ADPF_OWNERS
per-file IHintManager.aidl = file:/ADPF_OWNERS
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 5ac53f1..e5340a7 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,6 +4,15 @@
# keep-sorted start block=yes newline_separated=yes
flag {
+ # Holdback study for concurrent MessageQueue.
+ # Do not promote beyond trunkfood.
+ namespace: "system_performance"
+ name: "message_queue_force_legacy"
+ description: "Whether to holdback concurrent MessageQueue (force legacy)."
+ bug: "336880969"
+}
+
+flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
namespace: "game"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 23f7629..d5b5258 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10999,6 +10999,25 @@
"emergency_gesture_ui_last_started_millis";
/**
+ * Whether double tap the power button gesture is enabled.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED =
+ "double_tap_power_button_gesture_enabled";
+
+ /**
+ * Double tap power button gesture behavior.
+ * 0 = Camera launch
+ * 1 = Wallet launch
+ * @hide
+ */
+ @Readable
+ public static final String DOUBLE_TAP_POWER_BUTTON_GESTURE =
+ "double_tap_power_button_gesture";
+
+ /**
* Whether the camera launch gesture to double tap the power button when the screen is off
* should be disabled.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 072a835..b6e114b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8876,11 +8876,6 @@
SyntheticTouchNavigationHandler() {
super(true);
- int gestureDetectorVelocityStrategy =
- android.companion.virtual.flags.Flags
- .impulseVelocityStrategyForTouchNavigation()
- ? VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE
- : VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT;
mGestureDetector = new GestureDetector(mContext,
new GestureDetector.OnGestureListener() {
@Override
@@ -8920,7 +8915,7 @@
}
},
/* handler= */ null,
- gestureDetectorVelocityStrategy);
+ VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE);
}
public void process(MotionEvent event) {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 86bbeb8..ebbe483 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -441,3 +441,11 @@
description: "Enable Predictive Back Animation for 3-button-nav"
bug: "373544911"
}
+
+flag {
+ name: "predictive_back_default_enable_sdk_36"
+ namespace: "systemui"
+ description: "Enable Predictive Back by default with targetSdk>=36"
+ is_fixed_read_only: true
+ bug: "376407910"
+}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 8a6e6be..5fc1276 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2377,6 +2377,7 @@
* Flags are separated by type and by default value. They are sorted alphabetically within each
* section.
*/
+ @SuppressWarnings("AndroidFrameworkCompatChange")
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
int targetSdk = pkg.getTargetSdkVersion();
//@formatter:off
@@ -2414,12 +2415,20 @@
.setResetEnabledSettingsOnAppDataCleared(bool(false,
R.styleable.AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared,
sa))
- .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
.setCleartextTrafficAllowed(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
+ // CompatChange.isChangeEnabled() can't be used here because this is called during
+ // PackageManagerService initialization. PlatformCompat can't be used because this
+ // code is not guaranteed to be called from the system_server process. Therefore
+ // accessing Build.VERSION_CODES directly and suppressing
+ // AndroidFrameworkCompatChange warning
+ .setOnBackInvokedCallbackEnabled(bool(
+ com.android.window.flags.Flags.predictiveBackDefaultEnableSdk36()
+ && targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
// Ints Default 0
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
// Ints
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 6af742f..2e0fe9e 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -256,6 +256,12 @@
}
optional Display display = 100;
+ message DoubleTapPowerButton {
+ optional SettingProto gesture_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto gesture = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional DoubleTapPowerButton double_tap_power_button = 103;
+
message Doze {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -737,5 +743,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 103;
+ // Next tag = 104;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7f2c816..38ebda7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2731,6 +2731,8 @@
</string-array>
<!-- The list of supported dream complications -->
<integer-array name="config_supportedDreamComplications">
+ <!-- COMPLICATION_TYPE_TIME -->
+ <item>1</item>
</integer-array>
<!-- Are we allowed to dream while not plugged in? -->
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 6ac36a3..1bf6af8 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.bar
import android.app.ActivityManager
+import android.content.ComponentName
import android.content.Context
import android.content.pm.ShortcutInfo
import android.graphics.Insets
@@ -24,6 +25,7 @@
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
+import android.widget.FrameLayout
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -110,9 +112,9 @@
regionSamplingProvider = TestRegionSamplingProvider()
- bubbleExpandedView = (inflater.inflate(
+ bubbleExpandedView = inflater.inflate(
R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
- ) as BubbleBarExpandedView)
+ ) as BubbleBarExpandedView
bubbleExpandedView.initialize(
expandedViewManager,
positioner,
@@ -124,11 +126,11 @@
regionSamplingProvider,
)
- getInstrumentation().runOnMainSync(Runnable {
+ getInstrumentation().runOnMainSync {
bubbleExpandedView.onAttachedToWindow()
// Helper should be created once attached to window
testableRegionSamplingHelper = regionSamplingProvider!!.helper
- })
+ }
bubble = Bubble(
"key",
@@ -254,6 +256,93 @@
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @Test
+ fun animateExpansion_waitsUntilTaskCreated() {
+ var animated = false
+ bubbleExpandedView.animateExpansionWhenTaskViewVisible { animated = true }
+ assertThat(animated).isFalse()
+ bubbleExpandedView.onTaskCreated()
+ assertThat(animated).isTrue()
+ }
+
+ @Test
+ fun animateExpansion_taskViewAttachedAndVisible() {
+ val inflater = LayoutInflater.from(context)
+ val expandedView = inflater.inflate(
+ R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
+ ) as BubbleBarExpandedView
+ val taskView = FakeBubbleTaskViewFactory().create()
+ val taskViewParent = FrameLayout(context)
+ taskViewParent.addView(taskView.taskView)
+ taskView.listener.onTaskCreated(666, ComponentName(context, "BubbleBarExpandedViewTest"))
+ assertThat(taskView.isVisible).isTrue()
+
+ expandedView.initialize(
+ expandedViewManager,
+ positioner,
+ BubbleLogger(uiEventLoggerFake),
+ false /* isOverflow */,
+ taskView,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingProvider,
+ )
+
+ // the task view should be removed from its parent
+ assertThat(taskView.taskView.parent).isNull()
+
+ var animated = false
+ expandedView.animateExpansionWhenTaskViewVisible { animated = true }
+ assertThat(animated).isFalse()
+
+ // send an invisible signal to simulate the surface getting destroyed
+ expandedView.onContentVisibilityChanged(false)
+
+ // send a visible signal to simulate a new surface getting created
+ expandedView.onContentVisibilityChanged(true)
+
+ assertThat(taskView.taskView.parent).isEqualTo(expandedView)
+ assertThat(animated).isTrue()
+ }
+
+ @Test
+ fun animateExpansion_taskViewAttachedAndInvisible() {
+ val inflater = LayoutInflater.from(context)
+ val expandedView = inflater.inflate(
+ R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */
+ ) as BubbleBarExpandedView
+ val taskView = FakeBubbleTaskViewFactory().create()
+ val taskViewParent = FrameLayout(context)
+ taskViewParent.addView(taskView.taskView)
+ taskView.listener.onTaskCreated(666, ComponentName(context, "BubbleBarExpandedViewTest"))
+ assertThat(taskView.isVisible).isTrue()
+ taskView.listener.onTaskVisibilityChanged(666, false)
+ assertThat(taskView.isVisible).isFalse()
+
+ expandedView.initialize(
+ expandedViewManager,
+ positioner,
+ BubbleLogger(uiEventLoggerFake),
+ false /* isOverflow */,
+ taskView,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingProvider,
+ )
+
+ // the task view should be added to the expanded view
+ assertThat(taskView.taskView.parent).isEqualTo(expandedView)
+
+ var animated = false
+ expandedView.animateExpansionWhenTaskViewVisible { animated = true }
+ assertThat(animated).isFalse()
+
+ // send a visible signal to simulate a new surface getting created
+ expandedView.onContentVisibilityChanged(true)
+
+ assertThat(animated).isTrue()
+ }
+
private fun BubbleBarExpandedView.menuView(): BubbleBarMenuView {
return findViewByPredicate { it is BubbleBarMenuView }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 0044593..3d34cba 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -253,6 +253,7 @@
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
}
waitForExpandedViewAnimation()
@@ -276,6 +277,7 @@
getInstrumentation().runOnMainSync {
bubbleBarLayerView.showExpandedView(bubble)
+ bubble.bubbleBarExpandedView!!.onContentVisibilityChanged(true)
}
waitForExpandedViewAnimation()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
index 68fc0c9..a517a2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -42,6 +42,16 @@
var componentName: ComponentName? = null
private set
+ /**
+ * Whether the task view is visible and has a surface. Note that this does not check the alpha
+ * value of the task view.
+ *
+ * When this is `true` it is safe to start showing the task view. Otherwise if this is `false`
+ * callers should wait for it to be visible which will be indicated either by a call to
+ * [TaskView.Listener.onTaskCreated] or [TaskView.Listener.onTaskVisibilityChanged]. */
+ var isVisible = false
+ private set
+
/** [TaskView.Listener] for users of this class. */
var delegateListener: TaskView.Listener? = null
@@ -61,9 +71,12 @@
this@BubbleTaskView.taskId = taskId
isCreated = true
componentName = name
+ // when the task is created it is visible
+ isVisible = true
}
override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+ this@BubbleTaskView.isVisible = visible
delegateListener?.onTaskVisibilityChanged(taskId, visible)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 74c3748..a313bd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -161,6 +161,7 @@
updateExpandedView();
bbev.setAnimating(true);
+ bbev.setSurfaceZOrderedOnTop(true);
bbev.setContentVisibility(false);
bbev.setAlpha(0f);
bbev.setTaskViewAlpha(0f);
@@ -171,28 +172,29 @@
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
- mExpandedViewAlphaAnimator.start();
+ bbev.animateExpansionWhenTaskViewVisible(() -> {
+ mExpandedViewAlphaAnimator.start();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .addUpdateListener((target, values) -> {
- bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
- })
- .withEndActions(() -> {
- bbev.setAnimationMatrix(null);
- updateExpandedView();
- bbev.setSurfaceZOrderedOnTop(false);
- if (afterAnimation != null) {
- afterAnimation.run();
- }
- })
- .start();
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+ .spring(AnimatableScaleMatrix.SCALE_X,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .spring(AnimatableScaleMatrix.SCALE_Y,
+ AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+ mScaleInSpringConfig)
+ .addUpdateListener((target, values) -> {
+ bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
+ })
+ .withEndActions(() -> {
+ bbev.setAnimationMatrix(null);
+ updateExpandedView();
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ })
+ .start();
+ });
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 3764bcd..9fe7a97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -131,6 +131,11 @@
/** Current corner radius */
private float mCurrentCornerRadius = 0f;
+ /** A runnable to start the expansion animation as soon as the task view is made visible. */
+ @Nullable
+ private Runnable mAnimateExpansion = null;
+ private TaskViewVisibilityState mVisibilityState = TaskViewVisibilityState.INVISIBLE;
+
/**
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
* {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
@@ -140,6 +145,18 @@
private boolean mIsAnimating;
private boolean mIsDragging;
+ /** An enum value that tracks the visibility state of the task view */
+ private enum TaskViewVisibilityState {
+ /** The task view is going away, and we're waiting for the surface to be destroyed. */
+ PENDING_INVISIBLE,
+ /** The task view is invisible and does not have a surface. */
+ INVISIBLE,
+ /** The task view is in the process of being added to a surface. */
+ PENDING_VISIBLE,
+ /** The task view is visible and has a surface. */
+ VISIBLE
+ }
+
public BubbleBarExpandedView(Context context) {
this(context, null);
}
@@ -206,16 +223,27 @@
mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
/* listener= */ this, bubbleTaskView,
/* viewParent= */ this);
+
+ // if the task view is already attached to a parent we need to remove it
if (mTaskView.getParent() != null) {
+ // it's possible that the task view is visible, e.g. if we're unfolding, in which
+ // case removing it will trigger a visibility change. we have to wait for that
+ // signal before we can add it to this expanded view, otherwise the signal will be
+ // incorrect because the task view will have a surface.
+ // if the task view is not visible, then it has no surface and removing it will not
+ // trigger any visibility change signals.
+ if (bubbleTaskView.isVisible()) {
+ mVisibilityState = TaskViewVisibilityState.PENDING_INVISIBLE;
+ }
((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
}
- FrameLayout.LayoutParams lp =
- new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
- addView(mTaskView, lp);
- mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCurrentCornerRadius);
- mTaskView.setVisibility(VISIBLE);
- mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
+
+ // if we're invisible it's safe to setup the task view and then await on the visibility
+ // signal.
+ if (mVisibilityState == TaskViewVisibilityState.INVISIBLE) {
+ mVisibilityState = TaskViewVisibilityState.PENDING_VISIBLE;
+ setupTaskView();
+ }
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
@@ -269,6 +297,16 @@
});
}
+ private void setupTaskView() {
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ addView(mTaskView, lp);
+ mTaskView.setEnableSurfaceClipping(true);
+ mTaskView.setCornerRadius(mCurrentCornerRadius);
+ mTaskView.setVisibility(VISIBLE);
+ mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
+ }
+
public BubbleBarHandleView getHandleView() {
return mHandleView;
}
@@ -326,15 +364,28 @@
@Override
public void onTaskCreated() {
- setContentVisibility(true);
+ if (mTaskView != null) {
+ mTaskView.setAlpha(0);
+ }
if (mListener != null) {
mListener.onTaskCreated();
}
+ // when the task is created we're visible
+ onTaskViewVisible();
}
@Override
public void onContentVisibilityChanged(boolean visible) {
- setContentVisibility(visible);
+ if (mVisibilityState == TaskViewVisibilityState.PENDING_INVISIBLE && !visible) {
+ // the surface is now destroyed. set up the task view and wait for the visibility
+ // signal.
+ mVisibilityState = TaskViewVisibilityState.PENDING_VISIBLE;
+ setupTaskView();
+ return;
+ }
+ if (visible) {
+ onTaskViewVisible();
+ }
}
@Override
@@ -350,6 +401,25 @@
mListener.onBackPressed();
}
+ void animateExpansionWhenTaskViewVisible(Runnable animateExpansion) {
+ if (mVisibilityState == TaskViewVisibilityState.VISIBLE) {
+ animateExpansion.run();
+ } else {
+ mAnimateExpansion = animateExpansion;
+ }
+ }
+
+ private void onTaskViewVisible() {
+ // if we're waiting to be visible, start the expansion animation if it's pending.
+ if (mVisibilityState == TaskViewVisibilityState.PENDING_VISIBLE) {
+ mVisibilityState = TaskViewVisibilityState.VISIBLE;
+ if (mAnimateExpansion != null) {
+ mAnimateExpansion.run();
+ mAnimateExpansion = null;
+ }
+ }
+ }
+
/**
* Set whether this view is currently being dragged.
*
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 fbf68ff..635531e 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
@@ -29,6 +29,7 @@
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.UserManager;
@@ -893,7 +894,8 @@
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
- DesktopModeEventLogger desktopModeEventLogger
+ DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -906,7 +908,7 @@
assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
- focusTransitionObserver, desktopModeEventLogger));
+ focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger));
}
@WMSingleton
@@ -1234,9 +1236,10 @@
@WMSingleton
@Provides
static DesktopModeUiEventLogger provideDesktopUiEventLogger(
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ PackageManager packageManager
) {
- return new DesktopModeUiEventLogger(uiEventLogger);
+ return new DesktopModeUiEventLogger(uiEventLogger, packageManager);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index dff56c1..5801c6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -16,6 +16,8 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.PackageManager
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEvent
@@ -27,6 +29,7 @@
/** Log Aster UIEvents for desktop windowing mode. */
class DesktopModeUiEventLogger(
private val uiEventLogger: UiEventLogger,
+ private val packageManager: PackageManager,
) {
private val instanceIdSequence = InstanceIdSequence(Integer.MAX_VALUE)
@@ -45,6 +48,17 @@
uiEventLogger.log(event, uid, packageName)
}
+ /** Logs an event for a CUI on a particular task. */
+ fun log(taskInfo: RunningTaskInfo, event: DesktopUiEventEnum) {
+ val packageName = taskInfo.baseActivity?.packageName
+ if (packageName == null) {
+ logD("Skip logging due to null base activity")
+ return
+ }
+ val uid = getUid(packageName, taskInfo.userId)
+ log(uid, packageName, event)
+ }
+
/** Retrieves a new instance id for a new interaction. */
fun getNewInstanceId(): InstanceId = instanceIdSequence.newInstanceId()
@@ -70,6 +84,12 @@
uiEventLogger.logWithInstanceId(event, uid, packageName, instanceId)
}
+ private fun getUid(packageName: String, userId: Int): Int = try {
+ packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid
+ } catch (e: PackageManager.NameNotFoundException) {
+ INVALID_PACKAGE_UID
+ }
+
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
@@ -84,12 +104,15 @@
@UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode")
DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723),
@UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
- DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724);
+ DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724),
+ @UiEvent(doc = "Tap on the window Handle to open the Handle Menu")
+ DESKTOP_WINDOW_APP_HANDLE_TAP(1998);
override fun getId(): Int = mId
}
companion object {
private const val TAG = "DesktopModeUiEventLogger"
+ private const val INVALID_PACKAGE_UID = -1
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 2bcbe30..5e6b51e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -371,7 +371,9 @@
// Update the src-rect-hint in params in place, to set up initial animator transform.
Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange);
- pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint);
+ final PictureInPictureParams params = getPipParams(pipChange);
+ params.copyOnlySet(
+ new PictureInPictureParams.Builder().setSourceRectHint(sourceRectHint).build());
// Config-at-end transitions need to have their activities transformed before starting
// the animation; this makes the buffer seem like it's been updated to final size.
@@ -416,9 +418,7 @@
final SurfaceControl pipLeash = getLeash(pipChange);
final Rect startBounds = pipChange.getStartAbsBounds();
final Rect endBounds = pipChange.getEndAbsBounds();
- final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams != null
- ? pipChange.getTaskInfo().pictureInPictureParams
- : new PictureInPictureParams.Builder().build();
+ final PictureInPictureParams params = getPipParams(pipChange);
final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
pipActivityChange);
@@ -598,10 +598,10 @@
PictureInPictureParams params = null;
if (pipChange.getTaskInfo() != null) {
// single activity
- params = pipChange.getTaskInfo().pictureInPictureParams;
+ params = getPipParams(pipChange);
} else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
// multi activity
- params = parentBeforePip.getTaskInfo().pictureInPictureParams;
+ params = getPipParams(parentBeforePip);
}
final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
startBounds);
@@ -842,19 +842,25 @@
initActivityPos.y);
}
}
-
- @NonNull
- private SurfaceControl getLeash(TransitionInfo.Change change) {
- SurfaceControl leash = change.getLeash();
- Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
- return leash;
- }
-
void cacheAndStartTransitionAnimator(@NonNull ValueAnimator animator) {
mTransitionAnimator = animator;
mTransitionAnimator.start();
}
+ @NonNull
+ private static PictureInPictureParams getPipParams(@NonNull TransitionInfo.Change pipChange) {
+ return pipChange.getTaskInfo().pictureInPictureParams != null
+ ? pipChange.getTaskInfo().pictureInPictureParams
+ : new PictureInPictureParams.Builder().build();
+ }
+
+ @NonNull
+ private static SurfaceControl getLeash(TransitionInfo.Change change) {
+ SurfaceControl leash = change.getLeash();
+ Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+ return leash;
+ }
+
//
// Miscellaneous callbacks and listeners
//
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 43ed23b..6caef36 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
@@ -108,6 +108,8 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -228,6 +230,7 @@
private final TaskPositionerFactory mTaskPositionerFactory;
private final FocusTransitionObserver mFocusTransitionObserver;
private final DesktopModeEventLogger mDesktopModeEventLogger;
+ private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -257,7 +260,8 @@
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
- DesktopModeEventLogger desktopModeEventLogger) {
+ DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger) {
this(
context,
shellExecutor,
@@ -292,7 +296,8 @@
activityOrientationChangeHandler,
new TaskPositionerFactory(),
focusTransitionObserver,
- desktopModeEventLogger);
+ desktopModeEventLogger,
+ desktopModeUiEventLogger);
}
@VisibleForTesting
@@ -330,7 +335,8 @@
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
TaskPositionerFactory taskPositionerFactory,
FocusTransitionObserver focusTransitionObserver,
- DesktopModeEventLogger desktopModeEventLogger) {
+ DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -393,6 +399,7 @@
mTaskPositionerFactory = taskPositionerFactory;
mFocusTransitionObserver = focusTransitionObserver;
mDesktopModeEventLogger = desktopModeEventLogger;
+ mDesktopModeUiEventLogger = desktopModeUiEventLogger;
shellInit.addInitCallback(this::onInit, this);
}
@@ -803,6 +810,11 @@
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey(mDisplayId);
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
+ if (id == R.id.caption_handle && !decoration.mTaskInfo.isFreeform()) {
+ // Clicking the App Handle.
+ mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_TAP);
+ }
if (!decoration.isHandleMenuActive()) {
moveTaskToFront(decoration.mTaskInfo);
openHandleMenu(mTaskId);
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
index 176020f..6d12b00 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
@@ -49,6 +49,7 @@
"wm-flicker-common-assertions",
"launcher-helper-lib",
"launcher-aosp-tapl",
+ "com_android_wm_shell_flags_lib",
],
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 6d396ea..9c71510 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -17,12 +17,16 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
import com.android.wm.shell.flicker.utils.ICommonAssertions
import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
@@ -30,6 +34,7 @@
import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -44,8 +49,13 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) :
DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
+ @JvmField
+ @Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
override val transition: FlickerBuilder.() -> Unit
get() = {
defaultSetup(this)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 9c31b46..310c2d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
@@ -42,7 +43,9 @@
private int mParentTaskId = INVALID_TASK_ID;
private int mUid = INVALID_TASK_ID;
private int mTaskId = INVALID_TASK_ID;
+ private int mUserId = -1;
private Intent mBaseIntent = new Intent();
+ private ComponentName mBaseActivity = null;
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
private @WindowConfiguration.ActivityType int mTopActivityType = ACTIVITY_TYPE_STANDARD;
@@ -88,6 +91,12 @@
return this;
}
+ /** Sets the task info's user id. */
+ public TestRunningTaskInfoBuilder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
/**
* Set {@link ActivityManager.RunningTaskInfo#baseIntent} for the task info, by default
* an empty intent is assigned
@@ -97,6 +106,14 @@
return this;
}
+ /**
+ * Set {@link ActivityManager.RunningTaskInfo#baseActivity} for the task info.
+ */
+ public TestRunningTaskInfoBuilder setBaseActivity(@NonNull ComponentName activity) {
+ mBaseActivity = activity;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setActivityType(
@WindowConfiguration.ActivityType int activityType) {
mActivityType = activityType;
@@ -172,6 +189,8 @@
info.isTopActivityTransparent = mIsTopActivityTransparent;
info.numActivities = mNumActivities;
info.lastActiveTime = mLastActiveTime;
+ info.userId = mUserId;
+ info.baseActivity = mBaseActivity;
return info;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
index a8b2811..94698e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -17,16 +17,22 @@
package com.android.wm.shell.desktopmode
+import android.content.ComponentName
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Test class for [DesktopModeUiEventLogger]
@@ -39,11 +45,12 @@
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var logger: DesktopModeUiEventLogger
+ private val mockPackageManager: PackageManager = mock<PackageManager>()
@Before
fun setUp() {
uiEventLoggerFake = UiEventLoggerFake()
- logger = DesktopModeUiEventLogger(uiEventLoggerFake)
+ logger = DesktopModeUiEventLogger(uiEventLoggerFake, mockPackageManager)
}
@Test
@@ -100,10 +107,28 @@
assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
}
+ @Test
+ fun logWithTaskInfo_eventLogged() {
+ val event =
+ DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setUserId(USER_ID)
+ .setBaseActivity(ComponentName(PACKAGE_NAME, "test"))
+ .build()
+ whenever(mockPackageManager.getApplicationInfoAsUser(PACKAGE_NAME, /* flags= */ 0, USER_ID))
+ .thenReturn(ApplicationInfo().apply { uid = UID })
+ logger.log(taskInfo, event)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+ assertThat(uiEventLoggerFake[0].instanceId).isNull()
+ assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+ assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+ }
companion object {
private val INSTANCE_ID = InstanceId.fakeInstanceId(0)
private const val UID = 10
+ private const val USER_ID = 2
private const val PACKAGE_NAME = "com.foo"
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 91eaada..1670f2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -55,6 +55,7 @@
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
@@ -199,7 +200,8 @@
Optional.of(mockActivityOrientationChangeHandler),
mockTaskPositionerFactory,
mockFocusTransitionObserver,
- desktopModeEventLogger
+ desktopModeEventLogger,
+ mock<DesktopModeUiEventLogger>()
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 08b0dd3..54a87ad 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -640,6 +640,9 @@
boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af);
+ /* Returns a List<Integer> */
+ List getSpatializedChannelMasks();
+
void registerSpatializerCallback(in ISpatializerCallback cb);
void unregisterSpatializerCallback(in ISpatializerCallback cb);
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 4f94c3e..5038754 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,12 +16,12 @@
package android.media;
-
import static android.media.audio.Flags.FLAG_IAMF_DEFINITIONS_API;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
import static android.media.codec.Flags.FLAG_NUM_INPUT_SLOTS;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
-import static android.media.codec.Flags.FLAG_APV_SUPPORT;
+import static android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES;
import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -1796,6 +1796,27 @@
public static final String KEY_NUM_SLOTS = "num-slots";
/**
+ * A key describing the picture profile ID to be applied to {@link MediaCodec}.
+ * <p>
+ * The associated value is a string.
+ * <p>
+ * @see {@link android.media.quality.PictureProfile}
+ * @see {@link android.media.quality.PictureProfile#getProfileId}
+ */
+ @FlaggedApi(FLAG_APPLY_PICTURE_PROFILES)
+ public static final String KEY_PICTURE_PROFILE_ID = "picture-profile-id";
+
+ /**
+ * A key describing the picture profile instance to be applied to {@link MediaCodec}.
+ * <p>
+ * The associated value is an instance of {@link android.media.quality.PictureProfile}.
+ * <p>
+ * @see {@link android.media.quality.PictureProfile}
+ */
+ @FlaggedApi(FLAG_APPLY_PICTURE_PROFILES)
+ public static final String KEY_PICTURE_PROFILE_INSTANCE = "picture-profile-instance";
+
+ /**
* QpOffsetRect constitutes the metadata required for encoding a region of interest in an
* image or a video frame. The region of interest is represented by a rectangle. The four
* integer coordinates of the rectangle are stored in fields left, top, right, bottom.
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 99fcaf2..95c4ad3 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -16,7 +16,10 @@
package android.media;
+import static android.media.audio.Flags.FLAG_SPATIALIZER_CAPABILITIES;
+
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -35,6 +38,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -527,6 +531,28 @@
}
/**
+ * Returns a list of channel masks that represent the widest channel masks the spatializer
+ * is capable of rendering with individual channel positions.
+ * For instance a spatializer may only support virtual speaker positions for 5.1, it would
+ * therefore return {@link AudioFormat#CHANNEL_OUT_5POINT1}. But it would still return
+ * <code>true</code> when querying {@link #canBeSpatialized(AudioAttributes, AudioFormat)} it
+ * with a channel mask of {@link AudioFormat#CHANNEL_OUT_7POINT1POINT2}: the sound present
+ * in each channel would still be heard, but the sounds from the rear, side and top pairs would
+ * be mixed together, and be spatialized at the same location.
+ * @return a list of channel masks following the <code>CHANNEL_OUT_*</code> output channel
+ * definitions found in {@link AudioFormat}.
+ */
+ @FlaggedApi(FLAG_SPATIALIZER_CAPABILITIES)
+ public @NonNull List<Integer> getSpatializedChannelMasks() {
+ try {
+ return mAm.getService().getSpatializedChannelMasks();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying getSpatializedChannelMasks", e);
+ return Collections.emptyList();
+ }
+ }
+
+ /**
* Adds a listener to be notified of changes to the enabled state of the
* {@code Spatializer}.
* @param executor the {@code Executor} handling the callback
diff --git a/media/java/android/media/tv/extension/analog/IAnalogAttributeInterface.aidl b/media/java/android/media/tv/extension/analog/IAnalogAttributeInterface.aidl
new file mode 100644
index 0000000..550acba
--- /dev/null
+++ b/media/java/android/media/tv/extension/analog/IAnalogAttributeInterface.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.analog;
+
+/**
+ * @hide
+ */
+interface IAnalogAttributeInterface {
+ int getVersion();
+ void setColorSystemCapability(in String[] list);
+ String[] getColorSystemCapability();
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamAppInfoListener.aidl b/media/java/android/media/tv/extension/cam/ICamAppInfoListener.aidl
new file mode 100644
index 0000000..73ae209
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamAppInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ICamAppInfoListener {
+ void onCamAppInfoChanged(int slotId, in Bundle appInfo);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamAppInfoService.aidl b/media/java/android/media/tv/extension/cam/ICamAppInfoService.aidl
new file mode 100644
index 0000000..d3a03bc
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamAppInfoService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamAppInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ICamAppInfoService {
+ // Register ICamAppInfoListener to get CICAM Application Information updates.
+ void addCamAppInfoListener(ICamAppInfoListener listener);
+ // Unregister ICamAppInfoListener and stop get Application Information notify.
+ void removeCamAppInfoListener(ICamAppInfoListener listener);
+ // Get the Application Information of the CICAM.
+ int getCamAppInfo(int slotId, out Bundle appInfo);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamDrmInfoListener.aidl b/media/java/android/media/tv/extension/cam/ICamDrmInfoListener.aidl
new file mode 100644
index 0000000..c727837
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamDrmInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ICamDrmInfoListener {
+ void onCamDrmInfoChanged(int slotId, in Bundle camDrmInfo);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamHostControlAskReleaseReplyCallback.aidl b/media/java/android/media/tv/extension/cam/ICamHostControlAskReleaseReplyCallback.aidl
new file mode 100644
index 0000000..83f2c01
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamHostControlAskReleaseReplyCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+/**
+ * @hide
+ */
+oneway interface ICamHostControlAskReleaseReplyCallback {
+ void onAskReleaseReply(String sessionToken, int replyStatus);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamHostControlInfoListener.aidl b/media/java/android/media/tv/extension/cam/ICamHostControlInfoListener.aidl
new file mode 100644
index 0000000..f48ca57
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamHostControlInfoListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+/**
+ * @hide
+ */
+oneway interface ICamHostControlInfoListener {
+ void onCamHostControlInfoChanged(String sessionToken, int sessionStatus);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamHostControlService.aidl b/media/java/android/media/tv/extension/cam/ICamHostControlService.aidl
new file mode 100644
index 0000000..6f6c376
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamHostControlService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamHostControlAskReleaseReplyCallback;
+import android.media.tv.extension.cam.ICamHostControlInfoListener;
+
+/**
+ * @hide
+ */
+interface ICamHostControlService {
+ // Register the listener to monitor host control session updates.
+ void addCamHostcontrolInfoListener(ICamHostControlInfoListener listener);
+ // Unregister ICamHostControlInfoListener and stop monitoring.
+ void removeCamHostcontrolInfoListener(ICamHostControlInfoListener listener);
+ // Request CICAM to release the resource.
+ int sendCamHostControlAskRelease(String sessionToken,
+ ICamHostControlAskReleaseReplyCallback callback);
+ // Enable/disable the host control mode.
+ void setHostControlMode(String sessionToken, boolean enable);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlag.aidl b/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlag.aidl
new file mode 100644
index 0000000..25a78b9
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlag.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamHostControlTuneQuietlyFlagListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ICamHostControlTuneQuietlyFlag {
+ // Register listener to notify host control tune_quietly_flag.
+ void addHcTuneQuietlyFlagListener(ICamHostControlTuneQuietlyFlagListener listener);
+ // Remove listener and stop monitor host control tune_quietly_flag.
+ void removeHcTuneQuietlyFlagListener(ICamHostControlTuneQuietlyFlagListener listener);
+ // Returns host control tune_quietly_flag value.
+ Bundle getHcTuneQuietlyFlag(String sessionToken);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlagListener.aidl b/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlagListener.aidl
new file mode 100644
index 0000000..5967124
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamHostControlTuneQuietlyFlagListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+/**
+ * @hide
+ */
+oneway interface ICamHostControlTuneQuietlyFlagListener {
+ void onHcTuneQuietlyFlagChanged(String sessionToken, int tuneQuietlyFlag);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamInfoListener.aidl b/media/java/android/media/tv/extension/cam/ICamInfoListener.aidl
new file mode 100644
index 0000000..5410bff
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamInfoListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ICamInfoListener {
+ void onCamInfoChanged(int slotId, in Bundle updatedCamInfo);
+ void onSlotInfoChanged(int slotId, in Bundle updatedSlotInfo);
+ void onNewTypeCamInsert(int slotId, in Bundle newCamType);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamMonitoringService.aidl b/media/java/android/media/tv/extension/cam/ICamMonitoringService.aidl
new file mode 100644
index 0000000..7b8014c
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamMonitoringService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ICamMonitoringService {
+ // Register a listener for slot/CAM info updates.
+ void addCamInfoListener(in ICamInfoListener listener);
+ // Unregister a listener for slot/CAM info updates.
+ void removeCamInfoListener(in ICamInfoListener listener);
+ // Get CAM information for the specified slot.
+ Bundle getCamInfo(int slotId);
+ // Get slot information.
+ Bundle getSlotInfo(int slotId);
+ // Returns list of slot Ids.
+ int[] getSlotIds();
+ // Check if the country supports CAM.
+ boolean isCamSupported();
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamPinCapabilityListener.aidl b/media/java/android/media/tv/extension/cam/ICamPinCapabilityListener.aidl
new file mode 100644
index 0000000..f92304a
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamPinCapabilityListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ICamPinCapabilityListener {
+ void onCamPinCapabilityChanged(int slotId, in Bundle bundle);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamPinService.aidl b/media/java/android/media/tv/extension/cam/ICamPinService.aidl
new file mode 100644
index 0000000..3f6cb28
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamPinService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamPinCapabilityListener;
+import android.media.tv.extension.cam.ICamPinStatusListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ICamPinService {
+ // Register ICamPinCapabilityListener to get CICAM updates.
+ void addCamPinCapabilityListener(in ICamPinCapabilityListener listener);
+ // Unregister ICamPinCapabilityListener and stop monitor PIN status and PIN capability.
+ void removeCamPinCapabilityListener(in ICamPinCapabilityListener listener);
+ // Send the PinCode that needs to be validated by CICAM.
+ int requestCamPinValidation(int slotId, in int[] pinCode, in ICamPinStatusListener listener);
+ // Get the PIN capabilities of the CICAM.
+ int getCamPinCapability(int slotId, out Bundle camPinCapability);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamPinStatusListener.aidl b/media/java/android/media/tv/extension/cam/ICamPinStatusListener.aidl
new file mode 100644
index 0000000..efbc394
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamPinStatusListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ICamPinStatusListener {
+ void onCamPinValidationReply(int slotId, in Bundle bundle);
+}
diff --git a/media/java/android/media/tv/extension/cam/ICamProfileInterface.aidl b/media/java/android/media/tv/extension/cam/ICamProfileInterface.aidl
new file mode 100644
index 0000000..3f1d40c
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/ICamProfileInterface.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ICamProfileInterface {
+ // Get CAM service update information for special slot.
+ Bundle getCamServiceUpdateInfo(int slotNumber);
+ // Request CAM TIS resend cam info update broadcast message when APK boot up.
+ void requestResendProfileInfoBroadcastACON();
+}
diff --git a/media/java/android/media/tv/extension/cam/IContentControlService.aidl b/media/java/android/media/tv/extension/cam/IContentControlService.aidl
new file mode 100644
index 0000000..6db79ab
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/IContentControlService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.ICamDrmInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IContentControlService {
+ // Register the listener to notify the DRM info changed by the CICAM.
+ void addCamDrmInfoListener(in ICamDrmInfoListener listener);
+ // Unregister listener to stop monitor DRM Info.
+ void removeCamDrmInfoListener(in ICamDrmInfoListener listener);
+ // Get the DRM Info of current watching channel.
+ int getCamDrmInfo(int slotId, out Bundle camDrmInfo);
+}
diff --git a/media/java/android/media/tv/extension/cam/IEnterMenuErrorCallback.aidl b/media/java/android/media/tv/extension/cam/IEnterMenuErrorCallback.aidl
new file mode 100644
index 0000000..ff61ddc
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/IEnterMenuErrorCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+/**
+ * @hide
+ */
+oneway interface IEnterMenuErrorCallback {
+ void onAppInfoEnterMenuError();
+}
diff --git a/media/java/android/media/tv/extension/cam/IMmiInterface.aidl b/media/java/android/media/tv/extension/cam/IMmiInterface.aidl
new file mode 100644
index 0000000..17a2a9c
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/IMmiInterface.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.media.tv.extension.cam.IEnterMenuErrorCallback;
+import android.media.tv.extension.cam.IMmiSession;
+import android.media.tv.extension.cam.IMmiStatusCallback;
+
+
+/**
+ * @hide
+ */
+interface IMmiInterface {
+ // Open a session for MMI.
+ IMmiSession openSession(int slotId, IMmiStatusCallback callback);
+ // Request to display CI Module setup screen.
+ void appInfoEnterMenu(int slotId, IEnterMenuErrorCallback callback);
+}
diff --git a/media/java/android/media/tv/extension/cam/IMmiSession.aidl b/media/java/android/media/tv/extension/cam/IMmiSession.aidl
new file mode 100644
index 0000000..c4f2762
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/IMmiSession.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+/**
+ * @hide
+ */
+interface IMmiSession {
+ // Send user answers to CAM on the MMI Menu screen.
+ void setMenuListAnswer(int response);
+ // Send user answers to CAM on the MMI Enq screen.
+ void setEnquiryAnswer(int answerId, String answer);
+ // Send CloseMmi APDU to Cam.
+ void closeMmi();
+ // Release MMI session.
+ void close();
+}
diff --git a/media/java/android/media/tv/extension/cam/IMmiStatusCallback.aidl b/media/java/android/media/tv/extension/cam/IMmiStatusCallback.aidl
new file mode 100644
index 0000000..3e68ced
--- /dev/null
+++ b/media/java/android/media/tv/extension/cam/IMmiStatusCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.cam;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IMmiStatusCallback {
+ void onMmiEnq(in Bundle request);
+ void onMmiListMenu(in Bundle request);
+ void onMmiClose();
+}
diff --git a/media/java/android/media/tv/extension/event/IEventDownload.aidl b/media/java/android/media/tv/extension/event/IEventDownload.aidl
new file mode 100644
index 0000000..29c7553
--- /dev/null
+++ b/media/java/android/media/tv/extension/event/IEventDownload.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.event;
+
+import android.media.tv.extension.event.IEventDownloadListener;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * @hide
+ */
+interface IEventDownload {
+ // Create an event download session and return it as a Ibinder for DVB/DTMB
+ IBinder createSession(in Bundle eventDownloadParams, in IEventDownloadListener listener);
+}
diff --git a/media/java/android/media/tv/extension/event/IEventDownloadListener.aidl b/media/java/android/media/tv/extension/event/IEventDownloadListener.aidl
new file mode 100644
index 0000000..6d7d61f
--- /dev/null
+++ b/media/java/android/media/tv/extension/event/IEventDownloadListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.event;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IEventDownloadListener {
+ void onCompleted(in Bundle status);
+}
diff --git a/media/java/android/media/tv/extension/event/IEventDownloadSession.aidl b/media/java/android/media/tv/extension/event/IEventDownloadSession.aidl
new file mode 100644
index 0000000..fe7ee37
--- /dev/null
+++ b/media/java/android/media/tv/extension/event/IEventDownloadSession.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.event;
+
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IEventDownloadSession {
+ // Determine to execute barker channel or silent tune flow for related service type
+ int isBarkerOrSequentialDownloadByServiceType(in Bundle eventDownloadParams);
+ // Determine whether to start barker channel or silent tune flow.
+ int isBarkerOrSequentialDownloadByServiceRecord(in Bundle eventDownloadParams);
+ // Start event download.
+ void startTuningMultiplex(in Uri channelUri);
+ // Set active window channels.
+ void setActiveWindowChannelInfo(in Uri[] activeWinChannelInfos);
+ // Cancel barker channel or silent tune flow.
+ void cancel();
+ // Release barker channel or silent tune flow.
+ void release();
+}
diff --git a/media/java/android/media/tv/extension/event/IEventMonitor.aidl b/media/java/android/media/tv/extension/event/IEventMonitor.aidl
new file mode 100644
index 0000000..f6e7bd1
--- /dev/null
+++ b/media/java/android/media/tv/extension/event/IEventMonitor.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.event;
+
+import android.media.tv.extension.event.IEventMonitorListener;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IEventMonitor {
+ // Get present event information.
+ Bundle getPresentEventInfo(long channelDbId);
+ // Add present event information listener.
+ void addPresentEventInfoListener(in IEventMonitorListener listener);
+ // Remove present event information listener.
+ void removePresentEventInfoListener(in IEventMonitorListener listener);
+ // Get following event information.
+ Bundle getFollowingEventInfo(long channelDbId);
+ // Add following event information listener.
+ void addFollowingEventInfoListener(in IEventMonitorListener listener);
+ // Remove following event information listener.
+ void removeFollowingEventInfoListener(in IEventMonitorListener listener);
+ // Get SDT guidance information.
+ Bundle getSdtGuidanceInfo(long channelDbId);
+ // Set Event Background channel list info.
+ void setBgmTuneChannelInfo(in Uri[] tuneChannelInfos);
+}
diff --git a/media/java/android/media/tv/extension/event/IEventMonitorListener.aidl b/media/java/android/media/tv/extension/event/IEventMonitorListener.aidl
new file mode 100644
index 0000000..a00e542
--- /dev/null
+++ b/media/java/android/media/tv/extension/event/IEventMonitorListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.event;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IEventMonitorListener {
+ void onInfoChanged(long channelDbId, in Bundle eventinfo);
+}
diff --git a/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl b/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl
new file mode 100644
index 0000000..ff78aa4
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IFavoriteNetwork.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.IFavoriteNetworkListener;
+import android.os.Bundle;
+
+/**
+ * Country: Norway
+ * Broadcast Type: BROADCAST_TYPE_DVB_T
+ * (Operator: RiksTV)
+ *
+ * @hide
+ */
+interface IFavoriteNetwork {
+ // Get the favorite network information,If there are no conflicts, the array of Bundle is empty.
+ Bundle[] getFavoriteNetworks();
+ // Select and set one of two or more favorite networks detected by the service scan.
+ int setFavoriteNetwork(in Bundle favoriteNetworkSettings);
+ // Set the listener to be invoked when two or more favorite networks are detected.
+ int setListener(in IFavoriteNetworkListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl b/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl
new file mode 100644
index 0000000..6994224
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IFavoriteNetworkListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IFavoriteNetworkListener {
+ void onDetectFavoriteNetwork(in Bundle detectFavoriteNetworks);
+}
diff --git a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl
new file mode 100644
index 0000000..cdf6e23
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+/**
+ * @hide
+ */
+interface IHDPlusInfo {
+ // Specifying a HDPlusInfo and start a network scan.
+ int setHDPlusInfo(String isBlindScanContinue, String isHDMode);
+}
diff --git a/media/java/android/media/tv/extension/scan/ILcnConflict.aidl b/media/java/android/media/tv/extension/scan/ILcnConflict.aidl
new file mode 100644
index 0000000..5dff39e
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ILcnConflict.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.ILcnConflictListener;
+import android.os.Bundle;
+
+/**
+ * Country: Italy, France
+ * Broadcast Type: BROADCAST_TYPE_DVB_T
+ *
+ * @hide
+ */
+interface ILcnConflict {
+ // Get the LCN conflict groups information, If there are no conflicts, the array of Bundle is empty.
+ Bundle[] getLcnConflictGroups();
+ // Resolve LCN conflicts caused by service scans.
+ int resolveLcnConflict(in Bundle[] lcnConflictSettings);
+ // Set the listener to be invoked the LCN conflict event.
+ int setListener(in ILcnConflictListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl b/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl
new file mode 100644
index 0000000..6bbbeb8
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ILcnConflictListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ILcnConflictListener {
+ void onDetectLcnConflict(in Bundle detectLcnConflicts);
+}
diff --git a/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl b/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl
new file mode 100644
index 0000000..f9a9d34
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ILcnV2ChannelList.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.ILcnV2ChannelListListener;
+import android.os.Bundle;
+
+/**
+ * Country: (NorDig etc.)
+ * Broadcast Type: BROADCAST_TYPE_DVB_T, BROADCAST_TYPE_DVB_C
+ *
+ * @hide
+ */
+interface ILcnV2ChannelList {
+ // Get the LCN V2 channel list information. If there are no conflicts, the array of Bundle is empty.
+ Bundle[] getLcnV2ChannelLists();
+ // Select and set one of two or more LCN V2 channel list detected by the service scan.
+ int setLcnV2ChannelList(in Bundle lcnV2ChannelListSettings);
+ // Set the listener to be invoked when two or more LCN V2 channel list are detected.
+ int setListener(in ILcnV2ChannelListListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl b/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl
new file mode 100644
index 0000000..cbdb83c
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ILcnV2ChannelListListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ILcnV2ChannelListListener {
+ void onDetectLcnV2ChannelList(in Bundle detectLcnV2ChannelList);
+}
diff --git a/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl b/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl
new file mode 100644
index 0000000..770f866
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IOperatorDetection.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.IOperatorDetectionListener;
+import android.os.Bundle;
+
+/**
+ * Country: Any
+ * Broadcast Type: BROADCAST_TYPE_DVB_S
+ * (Operator: M7)
+ *
+ * @hide
+ */
+interface IOperatorDetection {
+ // Set the operator selected info for scanning.
+ int setOperatorDetection(in Bundle operatorSelected);
+ // Set the listener to be invoked when one or more operator detection has been detected by
+ // operator detection searches.
+ int setListener(in IOperatorDetectionListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl b/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl
new file mode 100644
index 0000000..7dcd461
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IOperatorDetectionListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+
+/**
+ * @hide
+ */
+oneway interface IOperatorDetectionListener {
+ void onDetectOperatorDetectionList(in Bundle[] detectOperatorDetectionList);
+}
diff --git a/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl b/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl
new file mode 100644
index 0000000..fe755f8
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IRegionChannelList.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.IRegionChannelListListener;
+
+/**
+ * @hide
+ */
+interface IRegionChannelList {
+ // Set the region channel list for scanning.
+ int setRegionChannelList(String regionChannelList);
+ // Set the listener to be invoked when one or more region channel list has been detected by
+ // region channel list searches.
+ int setListener(in IRegionChannelListListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl b/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl
new file mode 100644
index 0000000..06b0eb5
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IRegionChannelListListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+/**
+ * @hide
+ */
+oneway interface IRegionChannelListListener {
+ void onDetectRegionChannelList(in String[] detectRegionChannelList);
+}
diff --git a/media/java/android/media/tv/extension/scan/IScanInterface.aidl b/media/java/android/media/tv/extension/scan/IScanInterface.aidl
new file mode 100644
index 0000000..b44d1d2
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IScanInterface.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.IScanListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IScanInterface {
+ IBinder createSession(int broadcastType, String countryCode, String operator,
+ in IScanListener listener);
+ Bundle getParameters(int broadcastType, String countryCode, String operator,
+ in Bundle params);
+}
diff --git a/media/java/android/media/tv/extension/scan/IScanListener.aidl b/media/java/android/media/tv/extension/scan/IScanListener.aidl
new file mode 100644
index 0000000..2c4807f
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IScanListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IScanListener {
+ // notify events during scan.
+ void onEvent(in Bundle eventArgs);
+ // notify the scan progress.
+ void onScanProgress(String scanProgress, in Bundle scanProgressInfo);
+ // notify the scan completion.
+ void onScanCompleted(int scanResult);
+ // notify that the temporaily held channel list is stored.
+ void onStoreCompleted(int storeResult);
+}
diff --git a/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl b/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl
new file mode 100644
index 0000000..b8074fc
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IScanSatSearch.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+/**
+ * For satellite search function.
+ * @hide
+ */
+interface IScanSatSearch {
+ // Set currecnt LNB as customized LNB, default LNB is universal LNB
+ int setCustomizedLnb(String customizedLnb);
+}
diff --git a/media/java/android/media/tv/extension/scan/IScanSession.aidl b/media/java/android/media/tv/extension/scan/IScanSession.aidl
new file mode 100644
index 0000000..d42eca1
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/IScanSession.aidl
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IScanSession {
+ // Start a service scan.
+ int startScan(int broadcastType, String countryCode, String operator, in int[] frequency,
+ String scanType, String languageCode);
+ // Reset the scan information held in TIS.
+ int resetScan();
+ // Cancel scan.
+ int cancelScan();
+
+ // Get available interface for created ScanExtension interface.
+ String[] getAvailableExtensionInterfaceNames();
+ // Get extension interface for Scan.
+ IBinder getExtensionInterface(String name);
+
+ // Clear the results of the service scan from the service database.
+ int clearServiceList(in Bundle optionalClearParams);
+ // Store the results of the service scan from the service database.
+ int storeServiceList();
+ // Get a service information specified by the service information ID.
+ Bundle getServiceInfo(String serviceInfoId, in String[] keys);
+ // Get a service information ID list.
+ String[] getServiceInfoIdList();
+ // Get a list of service info by the filter.
+ Bundle getServiceInfoList(in Bundle filterInfo, in String[] keys);
+ // Update the service information.
+ int updateServiceInfo(in Bundle serviceInfo);
+ // Updates the service information for the specified service information ID in array list.
+ int updateServiceInfoByList(in Bundle[] serviceInfo);
+
+ /* DVBI specific functions */
+ // Get all of the serviceLists, parsed from Local TV storage, Broadcast, USB file discovery.
+ Bundle getServiceLists();
+ // Users choose one serviceList from the serviceLists, and install the services.
+ int setServiceList(int serviceListRecId);
+ // Get all of the packageData, parsed from the selected serviceList XML.
+ Bundle getPackageData();
+ // Choose the package using package id and install the corresponding services.
+ int setPackage(String packageId);
+ // Get all of the countryRegionData, parsed from the selected serviceList XML.
+ Bundle getCountryRegionData();
+ // Choose the countryRegion using countryRegion id, and install the corresponding services.
+ int setCountryRegion(String regionId);
+ // Get all of the regionData, parsed from the selected serviceList XML.
+ Bundle getRegionData();
+ // Choose the region using the regionData id, and install the corresponding services.
+ int setRegion(String regionId);
+
+ // Get unique session token for the scan.
+ String getSessionToken();
+ // Release scan resource, the register listener will be released.
+ int release();
+}
diff --git a/media/java/android/media/tv/extension/scan/ITargetRegion.aidl b/media/java/android/media/tv/extension/scan/ITargetRegion.aidl
new file mode 100644
index 0000000..417e122
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ITargetRegion.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.ITargetRegionListener;
+
+import android.os.Bundle;
+
+/**
+ * Country: U.K.
+ * Broadcast Type: BROADCAST_TYPE_DVB_T
+ *
+ * @hide
+ */
+interface ITargetRegion {
+ // Get the target regions information. If there are no conflicts, the array of Bundle is empty.
+ Bundle[] getTargetRegions();
+ // Select and set one of two or more target region detected by the service scan.
+ int setTargetRegion(in Bundle targetRegionSettings);
+ // Set the listener to be invoked when two or more regions are detected.
+ int setListener(in ITargetRegionListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl b/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl
new file mode 100644
index 0000000..9d6aa8e
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ITargetRegionListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface ITargetRegionListener {
+ void onDetectTargetRegion(in Bundle detectTargetRegions);
+}
diff --git a/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl b/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl
new file mode 100644
index 0000000..f25952c
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ITkgsInfo.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+import android.media.tv.extension.scan.ITkgsInfoListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ITkgsInfo {
+ int setPrefServiceList(String prefServiceList);
+ int setTkgsInfoListener(in ITkgsInfoListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl b/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl
new file mode 100644
index 0000000..e3dcf2d
--- /dev/null
+++ b/media/java/android/media/tv/extension/scan/ITkgsInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scan;
+
+/**
+ * @hide
+ */
+oneway interface ITkgsInfoListener {
+ void onServiceList(in String[] serviceList);
+ void onTableVersionUpdate(int tableVersion);
+ void onUserMessage(String strMessage);
+}
diff --git a/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdate.aidl b/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdate.aidl
new file mode 100644
index 0000000..bda60ed
--- /dev/null
+++ b/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdate.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scanbsu;
+
+import android.media.tv.extension.scanbsu.IScanBackgroundServiceUpdateListener;
+
+/**
+ * @hide
+ */
+interface IScanBackgroundServiceUpdate {
+ // Set the listener for background service update
+ // receives notifications for svl/tsl/nwl update during background service update.
+ void addBackgroundServiceUpdateListener(String clientToken,
+ in IScanBackgroundServiceUpdateListener listener);
+ // Remove the listener for background service update to stop receiving notifications
+ // for svl/tsl/nwl update during background service update.
+ void removeBackgroundServiceUpdateListener(in IScanBackgroundServiceUpdateListener listener);
+}
diff --git a/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdateListener.aidl b/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdateListener.aidl
new file mode 100644
index 0000000..d9bcbed
--- /dev/null
+++ b/media/java/android/media/tv/extension/scanbsu/IScanBackgroundServiceUpdateListener.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.scanbsu;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IScanBackgroundServiceUpdateListener {
+ // On background service update add/delete/update svl records.
+ void onChannelListUpdate(String sessionToken, out Bundle[] updateInfos);
+ // On background service update add/delete/update nwl records.
+ void onNetworkListUpdate(String sessionToken, out Bundle[] updateInfos);
+ // On background service update add/delete/update tsl records.
+ void onTransportStreamingListUpdate(String sessionToken, out Bundle[] updateInfos);
+}
diff --git a/media/java/android/media/tv/extension/screenmode/IScreenModeSettings.aidl b/media/java/android/media/tv/extension/screenmode/IScreenModeSettings.aidl
new file mode 100644
index 0000000..57f3b4a
--- /dev/null
+++ b/media/java/android/media/tv/extension/screenmode/IScreenModeSettings.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.screenmode;
+
+/**
+ * @hide
+ */
+interface IScreenModeSettings {
+ // Set screen mode information using a JSON string.
+ void setScreenModeSettings(String sessionToken, String setting);
+ // Get the overscan index which TIS session is applied.
+ int getOverScanIndex(String sessionToken);
+ // Get status that TIS session is support overscan or not.
+ boolean getSupportApplyOverScan(String sessionToken);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IChannelListTransfer.aidl b/media/java/android/media/tv/extension/servicedb/IChannelListTransfer.aidl
new file mode 100644
index 0000000..cb6aecc
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IChannelListTransfer.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IChannelListTransfer {
+ // Parse XML file and import Channels information.
+ void importChannelList(in ParcelFileDescriptor pfd);
+ // Get Channels information for export and create XML file.
+ void exportChannelList(in ParcelFileDescriptor pfd);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceList.aidl b/media/java/android/media/tv/extension/servicedb/IServiceList.aidl
new file mode 100644
index 0000000..51daa80
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceList.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IServiceList {
+ // Get a list of the Service list IDs quivalent to COLUMN_CHANNEL_LIST_ID
+ // in the Channels table of TvProvider.
+ String[] getServiceListIds();
+ // Get the information associated with the Service list.
+ Bundle getServiceListInfo(String serviceListId, in String[] keys);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl
new file mode 100644
index 0000000..1b1577f
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.media.tv.extension.servicedb.IServiceListEditListener;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IServiceListEdit {
+ // Open in edit mode. Must call close() after edit is done.
+ int open(IServiceListEditListener listener);
+ // Method to close in edit mode.
+ int close();
+ // Method to commit changes made to service database.
+ int commit();
+ // Method to commit and close the changes.
+ int userEditCommit();
+
+ // Get a service/transportStream/Network/Satellite record information specified by
+ // serviceInfoId and keys from tvdb.
+ Bundle getServiceInfoFromDatabase(String serviceInfoId, in String[] keys);
+ // Get a list of all service records' information specified by serviceListId and keys from tvdb.
+ Bundle getServiceInfoListFromDatabase(String serviceListId, in String[] keys);
+ // Get a list of all service info IDs in the service list of serviceListId from tvdb.
+ String[] getServiceInfoIdsFromDatabase(String inServiceListId);
+ // Update a service information by the contents of serviceInfo;
+ int updateServiceInfoFromDatabase(in Bundle updateServiceInfo);
+ // Update all service information by the contents of serviceInfoList.
+ int updateServiceInfoByListFromDatabase(in Bundle[] updateServiceInfoList);
+ // Remove a service information of the serviceInfoId from the service list.
+ int removeServiceInfoFromDatabase(String serviceInfoId);
+ // Remove all service information of the serviceInfoId from the service list.
+ int removeServiceInfoByListFromDatabase(in String[] serviceInfoIdList);
+ // Get a list of the Service list IDs which is equivalent to COLUMN_CHANNEL_LIST_ID
+ // in Channels table from tv db.
+ String[] getServiceListChannelIds();
+ // Get the information associated with the Service list Channel id.
+ Bundle getServiceListInfoByChannelId(String serviceListChannelId, in String[] keys);
+
+ // Get a list of transportStream records' information specified by serviceListId and keys.
+ Bundle getTransportStreamInfoList(String serviceListId, in String[] keys);
+ // Get a list of transportStream records' information specified by serviceListId and keys
+ // from work db.
+ Bundle getTransportStreamInfoListForce(String serviceListId, in String[] keys);
+
+ // Get a list of network records' information specified by serviceListId and keys.
+ Bundle getNetworkInfoList(String serviceListId, in String[] keys);
+ // Get a list of satellite records' information specified by serviceListId and keys.
+ Bundle getSatelliteInfoList(String serviceListId, in String[] keys);
+
+ // Decompress whole bundle value of single service/transportStream/Network/Satellite record.
+ // RecordInfoBundle:a single record got from database by getServiceInfoFromDatabase()
+ String toRecordInfoByType(in Bundle recordInfoBundle, String recordType);
+ // Set channels(tv.db) modified result to middleware database(SVL/TSL/NWL/SATL).
+ int putRecordIdList(String serviceListId, in Bundle recordIdListBundle, int optType);
+
+ // Add predefined ServiceListInfo of Hotbird 13E in scan two satellite scene EU region
+ // following by commit().
+ String addPredefinedServiceListInfo(int broadcastType, String serviceListType,
+ String serviceListPrefix, String countryCode, int operatorId);
+ // Add predefined channels of Hotbird 13E in scan two satellite scene EU region.
+ int addPredefinedChannelList(String serviceListId, in Bundle[] predefinedListBundle);
+ // Add predefined satellite info of Hotbird 13E in scan two satellite scene EU region.
+ int addPredefinedSatInfo(String serviceListId, in Bundle predefinedSatInfoBundle);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListEditListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListEditListener.aidl
new file mode 100644
index 0000000..e227eda
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListEditListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+/**
+ * @hide
+ */
+oneway interface IServiceListEditListener {
+ void onCompleted(int requestId, int result);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListExportListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListExportListener.aidl
new file mode 100644
index 0000000..c57e8f9
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListExportListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+/**
+ * @hide
+ */
+oneway interface IServiceListExportListener {
+ void onExported(int exportResult);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListExportSession.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListExportSession.aidl
new file mode 100644
index 0000000..fcde581
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListExportSession.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IServiceListExportSession {
+ // Start export service list with reserved parameters.
+ int exportServiceList(in ParcelFileDescriptor pfd, in Bundle exportParams);
+ // Release export resources.
+ int release();
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl
new file mode 100644
index 0000000..abd8320
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+/**
+ * @hide
+ */
+interface IServiceListImportListener {
+ void onImported(int importResult);
+ void onPreloaded(int preloadResult);
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListImportSession.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListImportSession.aidl
new file mode 100644
index 0000000..1f1ae01
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListImportSession.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IServiceListImportSession {
+ // Start import service list. Should call after preload and before release.
+ int importServiceList(in ParcelFileDescriptor pfd, in Bundle importParams);
+ // Preparing for import.
+ int preload(in ParcelFileDescriptor pfd);
+ // Release import resources.
+ int release();
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListListener.aidl
new file mode 100644
index 0000000..7c9c5c8
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+/**
+ * @hide
+ */
+oneway interface IServiceListSetChannelListListener {
+ void onCompleted(int setChannelListResult);
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListSession.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListSession.aidl
new file mode 100644
index 0000000..b0527b3
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListSetChannelListSession.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface IServiceListSetChannelListSession {
+ // Set channelList with channelinfo bundles, serviceListInfo, and operation type.
+ int setChannelList(in Bundle[] channelsInfo, in Bundle ServiceListInfoBundle, int optType);
+ // Release set channellist resources.
+ int release();
+}
diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListTransferInterface.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListTransferInterface.aidl
new file mode 100644
index 0000000..91fb157
--- /dev/null
+++ b/media/java/android/media/tv/extension/servicedb/IServiceListTransferInterface.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.servicedb;
+
+import android.media.tv.extension.servicedb.IServiceListExportListener;
+import android.media.tv.extension.servicedb.IServiceListImportListener;
+import android.media.tv.extension.servicedb.IServiceListSetChannelListListener;
+import android.os.IBinder;
+
+/**
+ * @hide
+ */
+interface IServiceListTransferInterface {
+ IBinder createExportSession(in IServiceListExportListener listener);
+ IBinder createImportSession(in IServiceListImportListener listener);
+ IBinder createSetChannelListSession(in IServiceListSetChannelListListener listener);
+}
diff --git a/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfo.aidl b/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfo.aidl
new file mode 100644
index 0000000..a3725e4
--- /dev/null
+++ b/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfo.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.teletext;
+
+import android.media.tv.extension.teletext.IDataServiceSignalInfoListener;
+import android.os.Bundle;
+
+
+/**
+ * @hide
+ */
+interface IDataServiceSignalInfo {
+ // Get Teletext data service signal information.
+ Bundle getDataServiceSignalInfo(String sessionToken);
+ // Add a listener that receives notifications of teletext running information.
+ void addDataServiceSignalInfoListener(String clientToken,
+ IDataServiceSignalInfoListener listener);
+ // Remove a listener that receives notifications of Teletext running information.
+ void removeDataServiceSignalInfoListener(String clientToken,
+ IDataServiceSignalInfoListener listener);
+}
diff --git a/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfoListener.aidl b/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfoListener.aidl
new file mode 100644
index 0000000..0e99bf5
--- /dev/null
+++ b/media/java/android/media/tv/extension/teletext/IDataServiceSignalInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.teletext;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IDataServiceSignalInfoListener {
+ void onDataServiceSignalInfoChanged (String sessionToken, in Bundle changedSignalInfo);
+}
diff --git a/media/java/android/media/tv/extension/teletext/ITeletextPageSubCode.aidl b/media/java/android/media/tv/extension/teletext/ITeletextPageSubCode.aidl
new file mode 100644
index 0000000..c96ffc0
--- /dev/null
+++ b/media/java/android/media/tv/extension/teletext/ITeletextPageSubCode.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.teletext;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+interface ITeletextPageSubCode {
+ // Get Teletext page number
+ Bundle getTeletextPageNumber(String sessionToken);
+ // Set Teletext page number.
+ void setTeleltextPageNumber(String sessionToken, int pageNumber);
+ // Get Teletext sub page number.
+ Bundle getTeletextPageSubCode(String sessionToken);
+ // Set Teletext sub page number.
+ void setTeletextPageSubCode(String sessionToken, int pageSubCode);
+ // Get Teletext TopInfo.
+ Bundle getTeletextHasTopInfo(String sessionToken);
+ // Get Teletext TopBlockList.
+ Bundle getTeletextTopBlockList(String sessionToken);
+ // Get Teletext TopGroupList.
+ Bundle getTeletextTopGroupList(String sessionToken, int indexGroup);
+ // Get Teletext TopPageList.
+ Bundle getTeletextTopPageList(String sessionToken, int indexPage);
+}
diff --git a/media/java/android/media/tv/extension/tune/IChannelTunedInterface.aidl b/media/java/android/media/tv/extension/tune/IChannelTunedInterface.aidl
new file mode 100644
index 0000000..88e5084
--- /dev/null
+++ b/media/java/android/media/tv/extension/tune/IChannelTunedInterface.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.tune;
+
+import android.media.tv.extension.tune.IChannelTunedListener;
+
+/*
+* @hide
+*/
+interface IChannelTunedInterface {
+ void addChannelTunedListener(in IChannelTunedListener listener);
+ void removeChannelTunedListener(in IChannelTunedListener listener);
+}
diff --git a/media/java/android/media/tv/extension/tune/IChannelTunedListener.aidl b/media/java/android/media/tv/extension/tune/IChannelTunedListener.aidl
new file mode 100644
index 0000000..4687546
--- /dev/null
+++ b/media/java/android/media/tv/extension/tune/IChannelTunedListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.tune;
+
+import android.os.Bundle;
+
+/*
+* @hide
+*/
+oneway interface IChannelTunedListener {
+ void onChannelTuned(String sessionToken, in Bundle channelTunedInfo);
+}
diff --git a/media/java/android/media/tv/extension/tune/IMuxTune.aidl b/media/java/android/media/tv/extension/tune/IMuxTune.aidl
new file mode 100644
index 0000000..4e9dbda
--- /dev/null
+++ b/media/java/android/media/tv/extension/tune/IMuxTune.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.tune;
+
+import android.media.tv.extension.tune.IMuxTuneSession;
+
+/**
+* @hide
+*/
+interface IMuxTune {
+ IMuxTuneSession createSession(int broadcastType, String clientToken);
+}
diff --git a/media/java/android/media/tv/extension/tune/IMuxTuneSession.aidl b/media/java/android/media/tv/extension/tune/IMuxTuneSession.aidl
new file mode 100644
index 0000000..5ad72b4
--- /dev/null
+++ b/media/java/android/media/tv/extension/tune/IMuxTuneSession.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.tune;
+
+import android.os.Bundle;
+
+/**
+* @hide
+*/
+interface IMuxTuneSession {
+ // Start mux tune with tune params.
+ void start(int broadcastType, int frequency, int brandwith, in Bundle muxTuneParams);
+ // Stop mux tune.
+ void stop();
+ // Release muxtune resources.
+ void release();
+ // Get the session token created by TIS to identify different sessions.
+ String getSessionToken();
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 7ae2eafa..2aa73db 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -202,6 +202,7 @@
method public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+ method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public int getDefaultNfcSubscriptionId();
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 15814ed..1019a10 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -60,14 +60,15 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public android.nfc.T4tNdefNfcee getT4tNdefNfcee();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int);
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int resumePolling();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
@@ -83,6 +84,8 @@
field public static final int HCE_ACTIVATE = 1; // 0x1
field public static final int HCE_DATA_TRANSFERRED = 2; // 0x2
field public static final int HCE_DEACTIVATE = 3; // 0x3
+ field public static final int POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE = 2; // 0x2
+ field public static final int POLLING_STATE_CHANGE_SUCCEEDED = 1; // 0x1
field public static final int STATUS_OK = 0; // 0x0
field public static final int STATUS_UNKNOWN_ERROR = 1; // 0x1
}
@@ -120,6 +123,11 @@
@FlaggedApi("android.nfc.nfc_oem_extension") public abstract class NfcRoutingTableEntry {
method public int getNfceeId();
+ method public int getType();
+ field public static final int TYPE_AID = 0; // 0x0
+ field public static final int TYPE_PROTOCOL = 1; // 0x1
+ field public static final int TYPE_SYSTEM_CODE = 3; // 0x3
+ field public static final int TYPE_TECHNOLOGY = 2; // 0x2
}
@FlaggedApi("android.nfc.nfc_oem_extension") public final class OemLogItems implements android.os.Parcelable {
@@ -179,6 +187,47 @@
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int TECHNOLOGY_V = 3; // 0x3
}
+ @FlaggedApi("android.nfc.nfc_oem_extension") public final class T4tNdefNfcee {
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) @WorkerThread public int clearData();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isOperationOngoing();
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isSupported();
+ method @Nullable @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) @WorkerThread public android.nfc.T4tNdefNfceeCcFileInfo readCcfile();
+ method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) @WorkerThread public byte[] readData(@IntRange(from=0, to=65535) int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) @WorkerThread public int writeData(@IntRange(from=0, to=65535) int, @NonNull byte[]);
+ field public static final int CLEAR_DATA_FAILED_INTERNAL = 0; // 0x0
+ field public static final int CLEAR_DATA_SUCCESS = 1; // 0x1
+ field public static final int WRITE_DATA_ERROR_CONNECTION_FAILED = -6; // 0xfffffffa
+ field public static final int WRITE_DATA_ERROR_EMPTY_PAYLOAD = -7; // 0xfffffff9
+ field public static final int WRITE_DATA_ERROR_INTERNAL = -1; // 0xffffffff
+ field public static final int WRITE_DATA_ERROR_INVALID_FILE_ID = -4; // 0xfffffffc
+ field public static final int WRITE_DATA_ERROR_INVALID_LENGTH = -5; // 0xfffffffb
+ field public static final int WRITE_DATA_ERROR_NDEF_VALIDATION_FAILED = -8; // 0xfffffff8
+ field public static final int WRITE_DATA_ERROR_NFC_NOT_ON = -3; // 0xfffffffd
+ field public static final int WRITE_DATA_ERROR_RF_ACTIVATED = -2; // 0xfffffffe
+ field public static final int WRITE_DATA_SUCCESS = 0; // 0x0
+ }
+
+ @FlaggedApi("android.nfc.nfc_oem_extension") public final class T4tNdefNfceeCcFileInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=15, to=32767) public int getCcFileLength();
+ method @IntRange(from=0xffffffff, to=65535) public int getFileId();
+ method @IntRange(from=15, to=65535) public int getMaxReadLength();
+ method @IntRange(from=5, to=32767) public int getMaxSize();
+ method @IntRange(from=13, to=65535) public int getMaxWriteLength();
+ method public int getReadAccess();
+ method public int getVersion();
+ method public int getWriteAccess();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.T4tNdefNfceeCcFileInfo> CREATOR;
+ field public static final int READ_ACCESS_GRANTED_RESTRICTED = 128; // 0x80
+ field public static final int READ_ACCESS_GRANTED_UNRESTRICTED = 0; // 0x0
+ field public static final int VERSION_2_0 = 32; // 0x20
+ field public static final int VERSION_3_0 = 48; // 0x30
+ field public static final int WRITE_ACCESS_GRANTED_RESTRICTED = 128; // 0x80
+ field public static final int WRITE_ACCESS_GRANTED_UNRESTRICTED = 0; // 0x0
+ field public static final int WRITE_ACCESS_NOT_GRANTED = 255; // 0xff
+ }
+
}
package android.nfc.cardemulation {
@@ -186,14 +235,19 @@
public final class CardEmulation {
method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
- method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
- method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overrideRoutingTable(@NonNull android.app.Activity, int, int);
+ method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void recoverRoutingTable(@NonNull android.app.Activity);
+ method @FlaggedApi("android.nfc.enable_card_emulation_euicc") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setDefaultNfcSubscriptionId(int);
method @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setServiceEnabledForCategoryOther(@NonNull android.content.ComponentName, boolean);
field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_ALREADY_SET = 3; // 0x3
field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_FEATURE_UNSUPPORTED = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_INVALID_SERVICE = 2; // 0x2
field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_FAILURE_UNKNOWN_ERROR = 4; // 0x4
field @FlaggedApi("android.nfc.nfc_set_service_enabled_for_category_other") public static final int SET_SERVICE_ENABLED_STATUS_OK = 0; // 0x0
+ field @FlaggedApi("android.nfc.enable_card_emulation_euicc") public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_INTERNAL_ERROR = 2; // 0x2
+ field @FlaggedApi("android.nfc.enable_card_emulation_euicc") public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_INVALID_SUBSCRIPTION_ID = 1; // 0x1
+ field @FlaggedApi("android.nfc.enable_card_emulation_euicc") public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_NOT_SUPPORTED = 3; // 0x3
+ field @FlaggedApi("android.nfc.enable_card_emulation_euicc") public static final int SET_SUBSCRIPTION_ID_STATUS_SUCCESS = 0; // 0x0
}
}
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index a08b55f..13e6734 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -31,6 +31,7 @@
import android.nfc.INfcFCardEmulation;
import android.nfc.INfcOemExtensionCallback;
import android.nfc.INfcUnlockHandler;
+import android.nfc.IT4tNdefNfcee;
import android.nfc.ITagRemovedCallback;
import android.nfc.INfcDta;
import android.nfc.INfcWlcStateListener;
@@ -52,8 +53,8 @@
int getState();
boolean disable(boolean saveState, in String pkg);
boolean enable(in String pkg);
- void pausePolling(int timeoutInMs);
- void resumePolling();
+ int pausePolling(int timeoutInMs);
+ int resumePolling();
void setForegroundDispatch(in PendingIntent intent,
in IntentFilter[] filters, in TechListParcel techLists);
@@ -122,4 +123,5 @@
void indicateDataMigration(boolean inProgress, String pkg);
int commitRouting();
boolean isTagIntentAllowed(in String pkg, in int Userid);
+ IT4tNdefNfcee getT4tNdefNfceeInterface();
}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 633d8bf..bb9fe95 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -53,6 +53,8 @@
void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
void recoverRoutingTable(int userHandle);
boolean isEuiccSupported();
+ int getDefaultNfcSubscriptionId(in String pkg);
+ int setDefaultNfcSubscriptionId(int subscriptionId, in String pkg);
void setAutoChangeStatus(boolean state);
boolean isAutoChangeEnabled();
List<String> getRoutingStatus();
diff --git a/nfc/java/android/nfc/IT4tNdefNfcee.aidl b/nfc/java/android/nfc/IT4tNdefNfcee.aidl
new file mode 100644
index 0000000..b4cda5b
--- /dev/null
+++ b/nfc/java/android/nfc/IT4tNdefNfcee.aidl
@@ -0,0 +1,33 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2024 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+package android.nfc;
+
+import android.nfc.T4tNdefNfceeCcFileInfo;
+
+/**
+ * @hide
+ */
+interface IT4tNdefNfcee {
+ int writeData(in int fileId, in byte[] data);
+ byte[] readData(in int fileId);
+ int clearNdefData();
+ boolean isNdefOperationOngoing();
+ boolean isNdefNfceeEmulationSupported();
+ T4tNdefNfceeCcFileInfo readCcfile();
+}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 056844f..89ce423 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -589,6 +589,7 @@
static INfcTag sTagService;
static INfcCardEmulation sCardEmulationService;
static INfcFCardEmulation sNfcFCardEmulationService;
+ static IT4tNdefNfcee sNdefNfceeService;
/**
* The NfcAdapter object for each application context.
@@ -827,7 +828,13 @@
throw new UnsupportedOperationException();
}
}
-
+ try {
+ sNdefNfceeService = sService.getT4tNdefNfceeInterface();
+ } catch (RemoteException e) {
+ sNdefNfceeService = null;
+ Log.e(TAG, "could not retrieve NDEF NFCEE service");
+ throw new UnsupportedOperationException();
+ }
sIsInitialized = true;
}
NfcAdapter adapter = sNfcAdapters.get(context);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 9ed678f..ac7b8bb 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -173,6 +173,31 @@
public @interface HostCardEmulationAction {}
/**
+ * Status code returned when the polling state change request succeeded.
+ * @see #pausePolling()
+ * @see #resumePolling()
+ */
+ public static final int POLLING_STATE_CHANGE_SUCCEEDED = 1;
+ /**
+ * Status code returned when the polling state change request is already in
+ * required state.
+ * @see #pausePolling()
+ * @see #resumePolling()
+ */
+ public static final int POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE = 2;
+ /**
+ * Possible status codes for {@link #pausePolling()} and
+ * {@link #resumePolling()}.
+ * @hide
+ */
+ @IntDef(value = {
+ POLLING_STATE_CHANGE_SUCCEEDED,
+ POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PollingStateChangeStatusCode {}
+
+ /**
* Status OK
*/
public static final int STATUS_OK = 0;
@@ -467,6 +492,28 @@
}
/**
+ * Get an instance of {@link T4tNdefNfcee} object for performing T4T (Type-4 Tag)
+ * NDEF (NFC Data Exchange Format) NFCEE (NFC Execution Environment) operations.
+ * This can be used to write NDEF data to emulate a T4T tag in an NFCEE
+ * (NFC Execution Environment - eSE, SIM, etc). Refer to the NFC forum specification
+ * "NFCForum-TS-NCI-2.3 section 10.4" and "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ *
+ * This is a singleton object which shall be used by OEM extension module to do NDEF-NFCEE
+ * read/write operations.
+ *
+ * <p>Returns {@link T4tNdefNfcee}
+ * <p>Does not cause any RF activity and does not block.
+ * @return NFC Data Exchange Format (NDEF) NFC Execution Environment (NFCEE) object
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public T4tNdefNfcee getT4tNdefNfcee() {
+ return T4tNdefNfcee.getInstance();
+ }
+
+ /**
* Register an {@link Callback} to listen for NFC oem extension callbacks
* Multiple clients can register and callbacks will be invoked asynchronously.
*
@@ -653,24 +700,32 @@
/**
* Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
- * In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely
- * use {@link #resumePolling()} to resume the polling.
+ * In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely.
+ * Use {@link #resumePolling() to resume the polling.
* @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
+ * @return status of the operation
+ * @throws IllegalArgumentException if timeoutInMs value is invalid
+ * (0 < timeoutInMs < max).
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void pausePolling(@DurationMillisLong int timeoutInMs) {
- NfcAdapter.callService(() -> NfcAdapter.sService.pausePolling(timeoutInMs));
+ public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong int timeoutInMs) {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sService.pausePolling(timeoutInMs),
+ POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE);
}
/**
* Resumes default NFC tag reader mode polling for the current device state if polling is
* paused. Calling this while already in polling is a no-op.
+ * @return status of the operation
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void resumePolling() {
- NfcAdapter.callService(() -> NfcAdapter.sService.resumePolling());
+ public @PollingStateChangeStatusCode int resumePolling() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sService.resumePolling(),
+ POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE);
}
/**
diff --git a/nfc/java/android/nfc/NfcRoutingTableEntry.java b/nfc/java/android/nfc/NfcRoutingTableEntry.java
index 4e91377..c2cbbed 100644
--- a/nfc/java/android/nfc/NfcRoutingTableEntry.java
+++ b/nfc/java/android/nfc/NfcRoutingTableEntry.java
@@ -17,8 +17,12 @@
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.SystemApi;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Class to represent an entry of routing table. This class is abstract and extended by
* {@link RoutingTableTechnologyEntry}, {@link RoutingTableProtocolEntry},
@@ -30,10 +34,42 @@
@SystemApi
public abstract class NfcRoutingTableEntry {
private final int mNfceeId;
+ private final int mType;
+
+ /**
+ * AID routing table type.
+ */
+ public static final int TYPE_AID = 0;
+ /**
+ * Protocol routing table type.
+ */
+ public static final int TYPE_PROTOCOL = 1;
+ /**
+ * Technology routing table type.
+ */
+ public static final int TYPE_TECHNOLOGY = 2;
+ /**
+ * System Code routing table type.
+ */
+ public static final int TYPE_SYSTEM_CODE = 3;
+
+ /**
+ * Possible type of this routing table entry.
+ * @hide
+ */
+ @IntDef(prefix = "TYPE_", value = {
+ TYPE_AID,
+ TYPE_PROTOCOL,
+ TYPE_TECHNOLOGY,
+ TYPE_SYSTEM_CODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RoutingTableType {}
/** @hide */
- protected NfcRoutingTableEntry(int nfceeId) {
+ protected NfcRoutingTableEntry(int nfceeId, @RoutingTableType int type) {
mNfceeId = nfceeId;
+ mType = type;
}
/**
@@ -43,4 +79,13 @@
public int getNfceeId() {
return mNfceeId;
}
+
+ /**
+ * Get the type of this entry.
+ * @return an integer defined in {@link RoutingTableType}
+ */
+ @RoutingTableType
+ public int getType() {
+ return mType;
+ }
}
diff --git a/nfc/java/android/nfc/RoutingTableAidEntry.java b/nfc/java/android/nfc/RoutingTableAidEntry.java
index 7634fe3..bf697d6 100644
--- a/nfc/java/android/nfc/RoutingTableAidEntry.java
+++ b/nfc/java/android/nfc/RoutingTableAidEntry.java
@@ -20,7 +20,7 @@
import android.annotation.SystemApi;
/**
- * Represents an AID entry in current routing table.
+ * Represents an Application ID (AID) entry in current routing table.
* @hide
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@@ -30,7 +30,7 @@
/** @hide */
public RoutingTableAidEntry(int nfceeId, String value) {
- super(nfceeId);
+ super(nfceeId, TYPE_AID);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableProtocolEntry.java b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
index 0c5be7d..536de4d 100644
--- a/nfc/java/android/nfc/RoutingTableProtocolEntry.java
+++ b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
@@ -97,7 +97,7 @@
/** @hide */
public RoutingTableProtocolEntry(int nfceeId, @ProtocolValue int value) {
- super(nfceeId);
+ super(nfceeId, TYPE_PROTOCOL);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
index f87ad5f..f61892d 100644
--- a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
+++ b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
@@ -20,7 +20,9 @@
import android.annotation.SystemApi;
/**
- * Represents a system code entry in current routing table.
+ * Represents a system code entry in current routing table, where system codes are two-byte values
+ * used in NFC-F technology (a type of NFC communication) to identify specific
+ * device configurations.
* @hide
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@@ -30,7 +32,7 @@
/** @hide */
public RoutingTableSystemCodeEntry(int nfceeId, byte[] value) {
- super(nfceeId);
+ super(nfceeId, TYPE_SYSTEM_CODE);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
index f51a529..2dbc942 100644
--- a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
+++ b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
@@ -30,22 +30,27 @@
@SystemApi
public class RoutingTableTechnologyEntry extends NfcRoutingTableEntry {
/**
- * Technology-A
+ * Technology-A.
+ * <p>Tech-A is mostly used for payment and ticketing applications. It supports various
+ * Tag platforms including Type 1, Type 2 and Type 4A tags. </p>
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public static final int TECHNOLOGY_A = 0;
/**
- * Technology-B
+ * Technology-B which is based on ISO/IEC 14443-3 standard.
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public static final int TECHNOLOGY_B = 1;
/**
- * Technology-F
+ * Technology-F.
+ * <p>Tech-F is a standard which supports Type 3 Tags and NFC-DEP protocol etc.</p>
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public static final int TECHNOLOGY_F = 2;
/**
- * Technology-V
+ * Technology-V.
+ * <p>Tech-V is an NFC technology used for communication with passive tags that operate
+ * at a longer range than other NFC technologies. </p>
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public static final int TECHNOLOGY_V = 3;
@@ -73,7 +78,7 @@
/** @hide */
public RoutingTableTechnologyEntry(int nfceeId, @TechnologyValue int value) {
- super(nfceeId);
+ super(nfceeId, TYPE_TECHNOLOGY);
this.mValue = value;
}
diff --git a/nfc/java/android/nfc/T4tNdefNfcee.java b/nfc/java/android/nfc/T4tNdefNfcee.java
new file mode 100644
index 0000000..06d02c5
--- /dev/null
+++ b/nfc/java/android/nfc/T4tNdefNfcee.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used for performing T4T (Type-4 Tag) NDEF (NFC Data Exchange Format)
+ * NFCEE (NFC Execution Environment) operations.
+ * This can be used to write NDEF data to emulate a T4T tag in an NFCEE
+ * (NFC Execution Environment - eSE, SIM, etc). Refer to the NFC forum specification
+ * "NFCForum-TS-NCI-2.3 section 10.4" and "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+@SystemApi
+public final class T4tNdefNfcee {
+ private static final String TAG = "NdefNfcee";
+ static T4tNdefNfcee sNdefNfcee;
+
+ private T4tNdefNfcee() {
+ }
+
+ /**
+ * Helper to get an instance of this class.
+ *
+ * @return
+ * @hide
+ */
+ @NonNull
+ public static T4tNdefNfcee getInstance() {
+ if (sNdefNfcee == null) {
+ sNdefNfcee = new T4tNdefNfcee();
+ }
+ return sNdefNfcee;
+ }
+
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data is successful.
+ */
+ public static final int WRITE_DATA_SUCCESS = 0;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to unknown reasons.
+ */
+ public static final int WRITE_DATA_ERROR_INTERNAL = -1;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to ongoing rf activity.
+ */
+ public static final int WRITE_DATA_ERROR_RF_ACTIVATED = -2;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to Nfc off.
+ */
+ public static final int WRITE_DATA_ERROR_NFC_NOT_ON = -3;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to invalid file id.
+ */
+ public static final int WRITE_DATA_ERROR_INVALID_FILE_ID = -4;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to invalid length.
+ */
+ public static final int WRITE_DATA_ERROR_INVALID_LENGTH = -5;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to core connection create failure.
+ */
+ public static final int WRITE_DATA_ERROR_CONNECTION_FAILED = -6;
+ /**
+ * Return flag for {@link #writeData(int, byte[])}.
+ * It indicates write data fail due to empty payload.
+ */
+ public static final int WRITE_DATA_ERROR_EMPTY_PAYLOAD = -7;
+ /**
+ * Returns flag for {@link #writeData(int, byte[])}.
+ * It idicates write data fail due to invalid ndef format.
+ */
+ public static final int WRITE_DATA_ERROR_NDEF_VALIDATION_FAILED = -8;
+
+ /**
+ * Possible return values for {@link #writeData(int, byte[])}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "WRITE_DATA_" }, value = {
+ WRITE_DATA_SUCCESS,
+ WRITE_DATA_ERROR_INTERNAL,
+ WRITE_DATA_ERROR_RF_ACTIVATED,
+ WRITE_DATA_ERROR_NFC_NOT_ON,
+ WRITE_DATA_ERROR_INVALID_FILE_ID,
+ WRITE_DATA_ERROR_INVALID_LENGTH,
+ WRITE_DATA_ERROR_CONNECTION_FAILED,
+ WRITE_DATA_ERROR_EMPTY_PAYLOAD,
+ WRITE_DATA_ERROR_NDEF_VALIDATION_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteDataStatus{}
+
+ /**
+ * This API performs writes of T4T data to NFCEE.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread.</p>
+ *
+ * @param fileId File id (Refer NFC Forum Type 4 Tag Specification
+ * Section 4.2 File Identifiers and Access Conditions
+ * for more information) to which to write.
+ * @param data This should be valid Ndef Message format.
+ * Refer to Nfc forum NDEF specification NDEF Message section
+ * @return status of the operation.
+ * @hide
+ */
+ @SystemApi
+ @WorkerThread
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public @WriteDataStatus int writeData(@IntRange(from = 0, to = 65535) int fileId,
+ @NonNull byte[] data) {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.writeData(fileId, data), WRITE_DATA_ERROR_INTERNAL);
+ }
+
+ /**
+ * This API performs reading of T4T content of Nfcee.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread.</p>
+ *
+ * @param fileId File Id (Refer
+ * Section 4.2 File Identifiers and Access Conditions
+ * for more information) from which to read.
+ * @return - Returns Ndef message if success
+ * Refer to Nfc forum NDEF specification NDEF Message section
+ * @throws IllegalStateException if read fails because the fileId is invalid.
+ * @hide
+ */
+ @SystemApi
+ @WorkerThread
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public byte[] readData(@IntRange(from = 0, to = 65535) int fileId) {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.readData(fileId), null);
+ }
+
+ /**
+ * Return flag for {@link #clearNdefData()}.
+ * It indicates clear data is successful.
+ */
+ public static final int CLEAR_DATA_SUCCESS = 1;
+ /**
+ * Return flag for {@link #clearNdefData()}.
+ * It indicates clear data failed due to internal error while processing the clear.
+ */
+ public static final int CLEAR_DATA_FAILED_INTERNAL = 0;
+
+ /**
+ * Possible return values for {@link #clearNdefData()}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "CLEAR_DATA_" }, value = {
+ CLEAR_DATA_SUCCESS,
+ CLEAR_DATA_FAILED_INTERNAL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ClearDataStatus{}
+
+ /**
+ * This API will set all the T4T NDEF NFCEE data to zero.
+ *
+ * <p>This is an I/O operation and will block until complete. It must
+ * not be called from the main application thread.
+ *
+ * <p>This API can be called regardless of NDEF file lock state.
+ * </p>
+ * @return status of the operation
+ *
+ * @hide
+ */
+ @SystemApi
+ @WorkerThread
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public @ClearDataStatus int clearData() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.clearNdefData(), CLEAR_DATA_FAILED_INTERNAL);
+ }
+
+ /**
+ * Returns whether NDEF NFCEE operation is ongoing or not.
+ *
+ * @return true if NDEF NFCEE operation is ongoing, else false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isOperationOngoing() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.isNdefOperationOngoing(), false);
+ }
+
+ /**
+ * This Api is to check the status of NDEF NFCEE emulation feature is
+ * supported or not.
+ *
+ * @return true if NDEF NFCEE emulation feature is supported, else false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isSupported() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.isNdefNfceeEmulationSupported(), false);
+ }
+
+ /**
+ * This API performs reading of T4T NDEF NFCEE CC file content.
+ *
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" for more details.
+ *
+ * @return Returns CC file content if success or null if failed to read.
+ * @hide
+ */
+ @SystemApi
+ @WorkerThread
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Nullable
+ public T4tNdefNfceeCcFileInfo readCcfile() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sNdefNfceeService.readCcfile(), null);
+ }
+}
diff --git a/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.aidl b/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.aidl
new file mode 100644
index 0000000..f72f74e
--- /dev/null
+++ b/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+parcelable T4tNdefNfceeCcFileInfo;
+
diff --git a/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.java b/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.java
new file mode 100644
index 0000000..5fca052
--- /dev/null
+++ b/nfc/java/android/nfc/T4tNdefNfceeCcFileInfo.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used to represence T4T (Type-4 Tag) NDEF (NFC Data Exchange Format)
+ * NFCEE (NFC Execution Environment) CC (Capability Container) File data.
+ * The CC file stores metadata about the T4T tag being emulated.
+ *
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" for more details.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+@SystemApi
+public final class T4tNdefNfceeCcFileInfo implements Parcelable {
+ /**
+ * Indicates the size of this capability container (called “CC File”)<p>
+ */
+ private int mCcLength;
+ /**
+ * Indicates the mapping specification version<p>
+ */
+ private int mVersion;
+ /**
+ * Indicates the max data size by a single ReadBinary<p>
+ */
+ private int mMaxReadLength;
+ /**
+ * Indicates the max data size by a single UpdateBinary<p>
+ */
+ private int mMaxWriteLength;
+ /**
+ * Indicates the NDEF File Identifier<p>
+ */
+ private int mFileId;
+ /**
+ * Indicates the maximum Max NDEF file size<p>
+ */
+ private int mMaxSize;
+ /**
+ * Indicates the read access condition<p>
+ */
+ private int mReadAccess;
+ /**
+ * Indicates the write access condition<p>
+ */
+ private int mWriteAccess;
+
+ /**
+ * Constructor to be used by NFC service and internal classes.
+ * @hide
+ */
+ public T4tNdefNfceeCcFileInfo(int cclen, int version, int maxLe, int maxLc,
+ int ndefFileId, int ndefMaxSize,
+ int ndefReadAccess, int ndefWriteAccess) {
+ mCcLength = cclen;
+ mVersion = version;
+ mMaxWriteLength = maxLc;
+ mMaxReadLength = maxLe;
+ mFileId = ndefFileId;
+ mMaxSize = ndefMaxSize;
+ mReadAccess = ndefReadAccess;
+ mWriteAccess = ndefWriteAccess;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+
+ dest.writeInt(mCcLength);
+ dest.writeInt(mVersion);
+ dest.writeInt(mMaxWriteLength);
+ dest.writeInt(mMaxReadLength);
+ dest.writeInt(mFileId);
+ dest.writeInt(mMaxSize);
+ dest.writeInt(mReadAccess);
+ dest.writeInt(mWriteAccess);
+ }
+
+ /**
+ * Indicates the size of this capability container (called “CC File”).
+ *
+ * @return length of the CC file.
+ */
+ @IntRange(from = 0xf, to = 0x7fff)
+ public int getCcFileLength() {
+ return mCcLength;
+ }
+
+ /**
+ * T4T tag mapping version 2.0.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" for more details.
+ */
+ public static final int VERSION_2_0 = 0x20;
+ /**
+ * T4T tag mapping version 2.0.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" for more details.
+ */
+ public static final int VERSION_3_0 = 0x30;
+
+ /**
+ * Possible return values for {@link #getVersion()}.
+ * @hide
+ */
+ @IntDef(prefix = { "VERSION_" }, value = {
+ VERSION_2_0,
+ VERSION_3_0,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Version{}
+
+ /**
+ * Indicates the mapping version of the T4T tag supported.
+ *
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.5" for more details.
+ *
+ * @return version of the specification
+ */
+ @Version
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Indicates the max data size that can be read by a single invocation of
+ * {@link T4tNdefNfcee#readData(int)}.
+ *
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" MLe.
+ * @return max size of read (in bytes).
+ */
+ @IntRange(from = 0xf, to = 0xffff)
+ public int getMaxReadLength() {
+ return mMaxReadLength;
+ }
+
+ /**
+ * Indicates the max data size that can be written by a single invocation of
+ * {@link T4tNdefNfcee#writeData(int, byte[])}
+ *
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.4" MLc.
+ * @return max size of write (in bytes).
+ */
+ @IntRange(from = 0xd, to = 0xffff)
+ public int getMaxWriteLength() {
+ return mMaxWriteLength;
+ }
+
+ /**
+ * Indicates the NDEF File Identifier. This is the identifier used in the last invocation of
+ * {@link T4tNdefNfcee#writeData(int, byte[])}
+ *
+ * @return FileId of the data stored or -1 if no data is present.
+ */
+ @IntRange(from = -1, to = 65535)
+ public int getFileId() {
+ return mFileId;
+ }
+
+ /**
+ * Indicates the maximum size of T4T NDEF data that can be written to the NFCEE.
+ *
+ * @return max size of the contents.
+ */
+ @IntRange(from = 0x5, to = 0x7fff)
+ public int getMaxSize() {
+ return mMaxSize;
+ }
+
+ /**
+ * T4T tag read access granted without any security.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ */
+ public static final int READ_ACCESS_GRANTED_UNRESTRICTED = 0x0;
+ /**
+ * T4T tag read access granted with limited proprietary access only.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ */
+ public static final int READ_ACCESS_GRANTED_RESTRICTED = 0x80;
+
+ /**
+ * Possible return values for {@link #getVersion()}.
+ * @hide
+ */
+ @IntDef(prefix = { "READ_ACCESS_GRANTED_" }, value = {
+ READ_ACCESS_GRANTED_RESTRICTED,
+ READ_ACCESS_GRANTED_UNRESTRICTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReadAccess {}
+
+ /**
+ * Indicates the read access condition.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ * @return read access restriction
+ */
+ @ReadAccess
+ public int getReadAccess() {
+ return mReadAccess;
+ }
+
+ /**
+ * T4T tag write access granted without any security.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ */
+ public static final int WRITE_ACCESS_GRANTED_UNRESTRICTED = 0x0;
+ /**
+ * T4T tag write access granted with limited proprietary access only.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ */
+ public static final int WRITE_ACCESS_GRANTED_RESTRICTED = 0x80;
+ /**
+ * T4T tag write access not granted.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ */
+ public static final int WRITE_ACCESS_NOT_GRANTED = 0xFF;
+
+ /**
+ * Possible return values for {@link #getVersion()}.
+ * @hide
+ */
+ @IntDef(prefix = { "READ_ACCESS_GRANTED_" }, value = {
+ WRITE_ACCESS_GRANTED_RESTRICTED,
+ WRITE_ACCESS_GRANTED_UNRESTRICTED,
+ WRITE_ACCESS_NOT_GRANTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteAccess {}
+
+ /**
+ * Indicates the write access condition.
+ * Refer to the NFC forum specification "NFCForum-TS-T4T-1.1 section 4.2" for more details.
+ * @return write access restriction
+ */
+ @WriteAccess
+ public int getWriteAccess() {
+ return mWriteAccess;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<T4tNdefNfceeCcFileInfo> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public T4tNdefNfceeCcFileInfo createFromParcel(Parcel in) {
+
+ // NdefNfceeCcFileInfo fields
+ int cclen = in.readInt();
+ int version = in.readInt();
+ int maxLe = in.readInt();
+ int maxLc = in.readInt();
+ int ndefFileId = in.readInt();
+ int ndefMaxSize = in.readInt();
+ int ndefReadAccess = in.readInt();
+ int ndefWriteAccess = in.readInt();
+
+ return new T4tNdefNfceeCcFileInfo(cclen, version, maxLe, maxLc,
+ ndefFileId, ndefMaxSize,
+ ndefReadAccess, ndefWriteAccess);
+ }
+
+ @Override
+ public T4tNdefNfceeCcFileInfo[] newArray(int size) {
+ return new T4tNdefNfceeCcFileInfo[size];
+ }
+ };
+}
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 24ff7ab..cb364fb 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,6 +22,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -45,6 +46,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
import android.util.Log;
@@ -1010,6 +1012,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void overrideRoutingTable(
@NonNull Activity activity, @ProtocolAndTechnologyRoute int protocol,
@@ -1037,6 +1040,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
public void recoverRoutingTable(@NonNull Activity activity) {
if (!activity.isResumed()) {
@@ -1058,6 +1062,97 @@
}
/**
+ * Setting the default subscription ID succeeded.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public static final int SET_SUBSCRIPTION_ID_STATUS_SUCCESS = 0;
+
+ /**
+ * Setting the default subscription ID failed because the subscription ID is invalid.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_INVALID_SUBSCRIPTION_ID = 1;
+
+ /**
+ * Setting the default subscription ID failed because there was an internal error processing
+ * the request. For ex: NFC service died in the middle of handling the API.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_INTERNAL_ERROR = 2;
+
+ /**
+ * Setting the default subscription ID failed because this feature is not supported on the
+ * device.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public static final int SET_SUBSCRIPTION_ID_STATUS_FAILED_NOT_SUPPORTED = 3;
+
+ /** @hide */
+ @IntDef(prefix = "SET_SUBSCRIPTION_ID_STATUS_",
+ value = {
+ SET_SUBSCRIPTION_ID_STATUS_SUCCESS,
+ SET_SUBSCRIPTION_ID_STATUS_FAILED_INVALID_SUBSCRIPTION_ID,
+ SET_SUBSCRIPTION_ID_STATUS_FAILED_INTERNAL_ERROR,
+ SET_SUBSCRIPTION_ID_STATUS_FAILED_NOT_SUPPORTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SetSubscriptionIdStatus {}
+
+ /**
+ * Sets the system's default NFC subscription id.
+ *
+ * <p> For devices with multiple UICC/EUICC that is configured to be NFCEE, this sets the
+ * default UICC NFCEE that will handle NFC offhost CE transactoions </p>
+ *
+ * @param subscriptionId the default NFC subscription Id to set.
+ * @return status of the operation.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public @SetSubscriptionIdStatus int setDefaultNfcSubscriptionId(int subscriptionId) {
+ return callServiceReturn(() ->
+ sService.setDefaultNfcSubscriptionId(
+ subscriptionId, mContext.getPackageName()),
+ SET_SUBSCRIPTION_ID_STATUS_FAILED_INTERNAL_ERROR);
+ }
+
+ /**
+ * Returns the system's default NFC subscription id.
+ *
+ * <p> For devices with multiple UICC/EUICC that is configured to be NFCEE, this returns the
+ * default UICC NFCEE that will handle NFC offhost CE transactoions </p>
+ * <p> If the device has no UICC that can serve as NFCEE, this will return
+ * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.</p>
+ *
+ * @return the default NFC subscription Id if set,
+ * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_CARD_EMULATION_EUICC)
+ public int getDefaultNfcSubscriptionId() {
+ return callServiceReturn(() ->
+ sService.getDefaultNfcSubscriptionId(mContext.getPackageName()),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
* Returns the value of {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT}.
*
* @param context A context
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index fc48217..8e7b8dd5 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -456,7 +456,7 @@
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
- public void onPackageFailure(@NonNull List<VersionedPackage> packages,
+ public void notifyPackageFailure(@NonNull List<VersionedPackage> packages,
@FailureReasons int failureReason) {
if (packages == null) {
Slog.w(TAG, "Could not resolve a list of failing packages");
@@ -467,7 +467,7 @@
if (Flags.recoverabilityDetection()) {
if (now >= mLastMitigation
&& (now - mLastMitigation) < getMitigationWindowMs()) {
- Slog.i(TAG, "Skipping onPackageFailure mitigation");
+ Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
return;
}
}
@@ -494,7 +494,7 @@
ObserverInternal observer = mAllObservers.valueAt(oIndex);
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null
- && observer.onPackageFailureLocked(
+ && observer.notifyPackageFailureLocked(
versionedPackage.getPackageName())) {
MonitoredPackage p = observer.getMonitoredPackage(
versionedPackage.getPackageName());
@@ -693,7 +693,7 @@
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
// We rollback all available low impact rollbacks when crash is unattributable
- onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
+ notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
} else {
@@ -916,7 +916,7 @@
* effectively behave as if the explicit health check hasn't passed for {@code packageName}.
*
* <p> {@code packageName} can still be considered failed if reported by
- * {@link #onPackageFailureLocked} before the package expires.
+ * {@link #notifyPackageFailureLocked} before the package expires.
*
* <p> Triggered by components outside the system server when they are fully functional after an
* update.
@@ -1460,7 +1460,7 @@
* @hide
*/
@GuardedBy("sLock")
- public boolean onPackageFailureLocked(String packageName) {
+ public boolean notifyPackageFailureLocked(String packageName) {
if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
&& registeredObserver.mayObservePackage(packageName)) {
putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 129e47f..88fe36c 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -477,7 +477,7 @@
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
- public void onPackageFailure(@NonNull List<VersionedPackage> packages,
+ public void notifyPackageFailure(@NonNull List<VersionedPackage> packages,
@FailureReasons int failureReason) {
if (packages == null) {
Slog.w(TAG, "Could not resolve a list of failing packages");
@@ -488,7 +488,7 @@
if (Flags.recoverabilityDetection()) {
if (now >= mLastMitigation
&& (now - mLastMitigation) < getMitigationWindowMs()) {
- Slog.i(TAG, "Skipping onPackageFailure mitigation");
+ Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
return;
}
}
@@ -515,7 +515,7 @@
ObserverInternal observer = mAllObservers.valueAt(oIndex);
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null
- && observer.onPackageFailureLocked(
+ && observer.notifyPackageFailureLocked(
versionedPackage.getPackageName())) {
MonitoredPackage p = observer.getMonitoredPackage(
versionedPackage.getPackageName());
@@ -714,7 +714,7 @@
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
// We rollback all available low impact rollbacks when crash is unattributable
- onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
+ notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
} else {
@@ -926,7 +926,7 @@
* effectively behave as if the explicit health check hasn't passed for {@code packageName}.
*
* <p> {@code packageName} can still be considered failed if reported by
- * {@link #onPackageFailureLocked} before the package expires.
+ * {@link #notifyPackageFailureLocked} before the package expires.
*
* <p> Triggered by components outside the system server when they are fully functional after an
* update.
@@ -1253,7 +1253,7 @@
return;
}
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ notifyPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
});
}
@@ -1467,7 +1467,7 @@
* @hide
*/
@GuardedBy("mLock")
- public boolean onPackageFailureLocked(String packageName) {
+ public boolean notifyPackageFailureLocked(String packageName) {
if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
&& registeredObserver.mayObservePackage(packageName)) {
putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 2321097..b01b7c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -390,6 +390,16 @@
}
/**
+ * Get the {@link MediaRoute2Info.Type} of the device.
+ */
+ public int getRouteType() {
+ if (mRouteInfo == null) {
+ return TYPE_UNKNOWN;
+ }
+ return mRouteInfo.getType();
+ }
+
+ /**
* Checks if route's volume is fixed, if true, we should disable volume control for the device.
*
* @return route for this device is fixed.
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 927a1c59..1f291cd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -107,6 +107,8 @@
Settings.Secure.DISPLAY_WHITE_BALANCE_ENABLED,
Settings.Secure.SYNC_PARENT_SOUNDS,
Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
// ACCESSIBILITY_QS_TARGETS needs to be restored after ENABLED_ACCESSIBILITY_SERVICES
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6d73ee2..abd5b9a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -160,6 +160,9 @@
VALIDATORS.put(Secure.DISPLAY_WHITE_BALANCE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SYNC_PARENT_SOUNDS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+ new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.QS_TILES, TILE_LIST_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 600c36e..5ae11ba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2124,6 +2124,15 @@
SecureSettingsProto.Display.SCREEN_RESOLUTION_MODE);
p.end(displayToken);
+ final long doubleTapPowerButtonToken = p.start(SecureSettingsProto.DOUBLE_TAP_POWER_BUTTON);
+ dumpSetting(s, p,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
+ SecureSettingsProto.DoubleTapPowerButton.GESTURE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+ SecureSettingsProto.DoubleTapPowerButton.GESTURE);
+ p.end(doubleTapPowerButtonToken);
+
final long dozeToken = p.start(SecureSettingsProto.DOZE);
dumpSetting(s, p,
Settings.Secure.DOZE_ENABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index d835c5f..b0409c0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -33,6 +33,15 @@
final class WritableNamespaces {
public static final Set<String> ALLOWLIST =
new ArraySet<String>(Arrays.asList(
- "exo"
+ "captive_portal_login",
+ "connectivity",
+ "exo",
+ "nearby",
+ "netd_native",
+ "network_security",
+ "on_device_personalization",
+ "tethering",
+ "tethering_u_or_later_native",
+ "thread_network"
));
}
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
index 3350efc..5f81085 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -27,6 +27,7 @@
],
flags_packages: [
"android.security.flags-aconfig",
+ "android.permission.flags-aconfig",
],
platform_apis: true,
certificate: "platform",
@@ -51,5 +52,6 @@
manifest: "AndroidManifest.xml",
flags_packages: [
"android.security.flags-aconfig",
+ "android.permission.flags-aconfig",
],
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ac02af81..27e6bab 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -981,6 +981,14 @@
<uses-permission android:name="android.permission.READ_SYSTEM_PREFERENCES" />
<uses-permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES" />
+ <!-- Permissions required for CTS test - ActivityManagerForegroundServiceTypeTest -->
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE"
+ android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/>
+ <uses-permission android:name="android.permission.health.READ_OXYGEN_SATURATION"
+ android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/>
+ <uses-permission android:name="android.permission.health.READ_SKIN_TEMPERATURE"
+ android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/>
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 2d58c8c..a266e7e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -49,7 +49,6 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import javax.inject.Provider
-import kotlinx.coroutines.flow.collectLatest
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -117,7 +116,7 @@
) {
"invalid ContentKey: $actionableContentKey"
}
- actionableContent.userActions.collectLatest { userActions ->
+ viewModel.filteredUserActions(actionableContent.userActions).collect { userActions ->
userActionsByContentKey[actionableContentKey] =
viewModel.resolveSceneFamilies(userActions)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 05a0119..bfcde7d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -130,10 +130,6 @@
modifier: Modifier = Modifier,
) {
val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
- val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
- if (isDisabled) {
- return
- }
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutHeight = LocalDisplayCutout.current.height()
@@ -196,7 +192,7 @@
horizontalArrangement = Arrangement.End,
modifier =
Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
- .padding(horizontal = horizontalPadding)
+ .padding(horizontal = horizontalPadding),
) {
if (isLargeScreenLayout) {
ShadeCarrierGroup(
@@ -207,7 +203,7 @@
SystemIconContainer(
viewModel = viewModel,
isClickable = isLargeScreenLayout,
- modifier = Modifier.align(Alignment.CenterVertically)
+ modifier = Modifier.align(Alignment.CenterVertically),
) {
StatusIcons(
viewModel = viewModel,
@@ -217,7 +213,7 @@
modifier =
Modifier.align(Alignment.CenterVertically)
.padding(end = 6.dp)
- .weight(1f, fill = false)
+ .weight(1f, fill = false),
)
BatteryIcon(
createBatteryMeterViewController =
@@ -252,27 +248,15 @@
CutoutLocation.NONE,
CutoutLocation.RIGHT -> {
startPlaceable.placeRelative(x = 0, y = 0)
- endPlaceable.placeRelative(
- x = startPlaceable.width,
- y = 0,
- )
+ endPlaceable.placeRelative(x = startPlaceable.width, y = 0)
}
CutoutLocation.CENTER -> {
startPlaceable.placeRelative(x = 0, y = 0)
- endPlaceable.placeRelative(
- x = startPlaceable.width + cutoutWidthPx,
- y = 0,
- )
+ endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0)
}
CutoutLocation.LEFT -> {
- startPlaceable.placeRelative(
- x = cutoutWidthPx,
- y = 0,
- )
- endPlaceable.placeRelative(
- x = startPlaceable.width + cutoutWidthPx,
- y = 0,
- )
+ startPlaceable.placeRelative(x = cutoutWidthPx, y = 0)
+ endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0)
}
}
}
@@ -288,10 +272,6 @@
modifier: Modifier = Modifier,
) {
val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
- val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
- if (isDisabled) {
- return
- }
val useExpandedFormat by remember {
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
@@ -302,17 +282,14 @@
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
if (isPrivacyChipVisible) {
Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
- PrivacyChip(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.CenterEnd),
- )
+ PrivacyChip(viewModel = viewModel, modifier = Modifier.align(Alignment.CenterEnd))
}
}
Column(
verticalArrangement = Arrangement.Bottom,
modifier =
Modifier.fillMaxWidth()
- .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
+ .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
Box {
@@ -362,11 +339,7 @@
}
@Composable
-private fun SceneScope.Clock(
- scale: Float,
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier,
-) {
+private fun SceneScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) {
val layoutDirection = LocalLayoutDirection.current
Element(key = ShadeHeader.Elements.Clock, modifier = modifier) {
@@ -391,10 +364,10 @@
LayoutDirection.Ltr -> 0f
LayoutDirection.Rtl -> 1f
},
- 0.5f
+ 0.5f,
)
}
- .clickable { viewModel.onClockClicked() }
+ .clickable { viewModel.onClockClicked() },
)
}
}
@@ -447,10 +420,7 @@
}
@Composable
-private fun ShadeCarrierGroup(
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
Row(modifier = modifier) {
val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle()
@@ -465,11 +435,11 @@
viewModel =
(viewModel.mobileIconsViewModel.viewModelForSub(
subId,
- StatusBarLocation.SHADE_CARRIER_GROUP
+ StatusBarLocation.SHADE_CARRIER_GROUP,
) as ShadeCarrierGroupMobileIconViewModel),
)
.also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
- },
+ }
)
}
}
@@ -506,7 +476,7 @@
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary),
Utils.getColorAttrDefaultColor(
themedContext,
- android.R.attr.textColorPrimaryInverse
+ android.R.attr.textColorPrimaryInverse,
),
)
statusBarIconController.addIconGroup(iconManager)
@@ -551,7 +521,7 @@
viewModel: ShadeHeaderViewModel,
isClickable: Boolean,
modifier: Modifier = Modifier,
- content: @Composable RowScope.() -> Unit
+ content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val isHovered by interactionSource.collectIsHoveredAsState()
@@ -578,10 +548,7 @@
}
@Composable
-private fun SceneScope.PrivacyChip(
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun SceneScope.PrivacyChip(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle()
AndroidView(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt
new file mode 100644
index 0000000..08225a77
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DisabledContentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val underTest = kosmos.disabledContentInteractor
+
+ @Test
+ fun isDisabled_notificationsShade() =
+ kosmos.runTest {
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NONE)
+ assertThat(underTest.isDisabled(Overlays.NotificationsShade)).isFalse()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(underTest.isDisabled(Overlays.NotificationsShade)).isTrue()
+ }
+
+ @Test
+ fun isDisabled_qsShade() =
+ kosmos.runTest {
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NONE)
+ assertThat(underTest.isDisabled(Overlays.QuickSettingsShade)).isFalse()
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(underTest.isDisabled(Overlays.QuickSettingsShade)).isTrue()
+ }
+
+ @Test
+ fun repeatWhenDisabled() =
+ kosmos.runTest {
+ var notificationDisabledCount = 0
+ applicationCoroutineScope.launch {
+ underTest.repeatWhenDisabled(Overlays.NotificationsShade) {
+ notificationDisabledCount++
+ }
+ }
+ var qsDisabledCount = 0
+ applicationCoroutineScope.launch {
+ underTest.repeatWhenDisabled(Overlays.QuickSettingsShade) { qsDisabledCount++ }
+ }
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(notificationDisabledCount).isEqualTo(0)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 =
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE or
+ StatusBarManager.DISABLE2_QUICK_SETTINGS
+ )
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(1)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(notificationDisabledCount).isEqualTo(1)
+ assertThat(qsDisabledCount).isEqualTo(2)
+ }
+
+ @Test
+ fun filteredUserActions() =
+ kosmos.runTest {
+ val map =
+ mapOf<UserAction, UserActionResult>(
+ Swipe.Up to UserActionResult.ShowOverlay(Overlays.NotificationsShade),
+ Swipe.Down to UserActionResult.ShowOverlay(Overlays.QuickSettingsShade),
+ )
+ val unfiltered = MutableStateFlow(map)
+ val filtered by collectLastValue(underTest.filteredUserActions(unfiltered))
+ assertThat(filtered).isEqualTo(map)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(filtered)
+ .isEqualTo(
+ mapOf(Swipe.Down to UserActionResult.ShowOverlay(Overlays.QuickSettingsShade))
+ )
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(filtered)
+ .isEqualTo(
+ mapOf(Swipe.Up to UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+ )
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(
+ disable2 =
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE or
+ StatusBarManager.DISABLE2_QUICK_SETTINGS
+ )
+ assertThat(filtered).isEqualTo(emptyMap<UserAction, UserActionResult>())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 7fe3d8d..48edded 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene.domain.interactor
+import android.app.StatusBarManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -30,6 +31,8 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.Transition
@@ -43,6 +46,8 @@
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -523,4 +528,51 @@
assertThat(currentScene).isEqualTo(Scenes.Gone)
}
+
+ @Test
+ fun showOverlay_overlayDisabled_doesNothing() =
+ kosmos.runTest {
+ val currentOverlays by collectLastValue(underTest.currentOverlays)
+ val disabledOverlay = Overlays.QuickSettingsShade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(disabledContentInteractor.isDisabled(disabledOverlay)).isTrue()
+ assertThat(currentOverlays).doesNotContain(disabledOverlay)
+
+ underTest.showOverlay(disabledOverlay, "reason")
+
+ assertThat(currentOverlays).doesNotContain(disabledOverlay)
+ }
+
+ @Test
+ fun replaceOverlay_withDisabledOverlay_doesNothing() =
+ kosmos.runTest {
+ val currentOverlays by collectLastValue(underTest.currentOverlays)
+ val showingOverlay = Overlays.NotificationsShade
+ underTest.showOverlay(showingOverlay, "reason")
+ assertThat(currentOverlays).isEqualTo(setOf(showingOverlay))
+ val disabledOverlay = Overlays.QuickSettingsShade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_QUICK_SETTINGS)
+ assertThat(disabledContentInteractor.isDisabled(disabledOverlay)).isTrue()
+
+ underTest.replaceOverlay(showingOverlay, disabledOverlay, "reason")
+
+ assertThat(currentOverlays).isEqualTo(setOf(showingOverlay))
+ }
+
+ @Test
+ fun changeScene_toDisabledScene_doesNothing() =
+ kosmos.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ val disabledScene = Scenes.Shade
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ assertThat(disabledContentInteractor.isDisabled(disabledScene)).isTrue()
+ assertThat(currentScene).isNotEqualTo(disabledScene)
+
+ underTest.changeScene(disabledScene, "reason")
+
+ assertThat(currentScene).isNotEqualTo(disabledScene)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index cca847e..5d49c11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -81,6 +81,9 @@
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -101,6 +104,8 @@
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
@@ -2673,6 +2678,25 @@
assertThat(isAlternateBouncerVisible).isFalse()
}
+ @Test
+ fun handleDisableFlags() =
+ kosmos.runTest {
+ underTest.start()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ sceneInteractor.changeScene(Scenes.Shade, "reason")
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+ fakeDisableFlagsRepository.disableFlags.value =
+ DisableFlagsModel(disable2 = StatusBarManager.DISABLE2_NOTIFICATION_SHADE)
+ runCurrent()
+
+ assertThat(currentScene).isNotEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index 8ef1e56..08a22a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import android.content.Context
+import android.content.MutableContextWrapper
import android.content.res.Configuration
import android.content.res.Resources
import android.view.Display
@@ -66,11 +67,12 @@
ShadeDisplaysInteractor(
shadeRootview,
positionRepository,
- defaultContext,
+ MutableContextWrapper(defaultContext),
+ resources,
contextStore,
- testScope,
+ testScope.backgroundScope,
configurationForwarder,
- testScope.coroutineContext,
+ testScope.backgroundScope.coroutineContext,
)
@Before
@@ -79,7 +81,6 @@
whenever(display.displayId).thenReturn(0)
whenever(resources.configuration).thenReturn(configuration)
- whenever(resources.configuration).thenReturn(configuration)
whenever(defaultContext.displayId).thenReturn(0)
whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm)
@@ -124,7 +125,6 @@
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(1)
interactor.start()
- testScope.advanceUntilIdle()
verify(defaultWm).removeView(eq(shadeRootview))
verify(secondaryWm).addView(eq(shadeRootview), any())
@@ -135,10 +135,8 @@
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(0)
interactor.start()
- testScope.advanceUntilIdle()
positionRepository.setDisplayId(1)
- testScope.advanceUntilIdle()
verify(defaultWm).removeView(eq(shadeRootview))
verify(secondaryWm).addView(eq(shadeRootview), any())
@@ -149,10 +147,8 @@
whenever(display.displayId).thenReturn(0)
positionRepository.setDisplayId(0)
interactor.start()
- testScope.advanceUntilIdle()
positionRepository.setDisplayId(1)
- testScope.advanceUntilIdle()
verify(configurationForwarder).onConfigurationChanged(eq(configuration))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index 1f29255..544d201 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -24,12 +25,18 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
import android.graphics.Color;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,11 +45,14 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -136,6 +146,60 @@
assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
}
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void testIncludePromotedOngoingInSection_flagEnabled() {
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
+
+ // THEN the entry is in the fgs section
+ assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
+ }
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void testDiscludePromotedOngoingInSection_flagDisabled() {
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
+
+ // THEN the entry is NOT in the fgs section
+ assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void promoterSelectsPromotedOngoing_flagEnabled() {
+ ArgumentCaptor<NotifPromoter> captor = ArgumentCaptor.forClass(NotifPromoter.class);
+ verify(mNotifPipeline).addPromoter(captor.capture());
+ NotifPromoter promoter = captor.getValue();
+
+ // GIVEN the notification has FLAG_PROMOTED_ONGOING
+ mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, true);
+
+ // THEN the entry is promoted to top level
+ assertTrue(promoter.shouldPromoteToTopLevel(mEntryBuilder.build()));
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void promoterIgnoresNonPromotedOngoing_flagEnabled() {
+ ArgumentCaptor<NotifPromoter> captor = ArgumentCaptor.forClass(NotifPromoter.class);
+ verify(mNotifPipeline).addPromoter(captor.capture());
+ NotifPromoter promoter = captor.getValue();
+
+ // GIVEN the notification does not have FLAG_PROMOTED_ONGOING
+ mEntryBuilder.setFlag(mContext, FLAG_PROMOTED_ONGOING, false);
+
+ // THEN the entry is NOT promoted to top level
+ assertFalse(promoter.shouldPromoteToTopLevel(mEntryBuilder.build()));
+ }
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void noPromoterAdded_flagDisabled() {
+ verify(mNotifPipeline, never()).addPromoter(any());
+ }
+
private Notification.CallStyle makeCallStyle() {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent("action"), PendingIntent.FLAG_IMMUTABLE);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index a940ed4..f48fd3c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -838,27 +838,26 @@
testScope.runTest {
var notificationCount = 10
val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
- val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
+ val config by collectLastValue(underTest.getLockscreenDisplayConfig(calculateSpace))
advanceTimeBy(50L)
showLockscreen()
shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
- assertThat(maxNotifications).isEqualTo(10)
+ assertThat(config?.maxNotifications).isEqualTo(10)
// Also updates when directly requested (as it would from NotificationStackScrollLayout)
notificationCount = 25
sharedNotificationContainerInteractor.notificationStackChanged()
advanceTimeBy(50L)
- assertThat(maxNotifications).isEqualTo(25)
+ assertThat(config?.maxNotifications).isEqualTo(25)
// Also ensure another collection starts with the same value. As an example, folding
// then unfolding will restart the coroutine and it must get the last value immediately.
- val newMaxNotifications by
- collectLastValue(underTest.getMaxNotifications(calculateSpace))
+ val newConfig by collectLastValue(underTest.getLockscreenDisplayConfig(calculateSpace))
advanceTimeBy(50L)
- assertThat(newMaxNotifications).isEqualTo(25)
+ assertThat(newConfig?.maxNotifications).isEqualTo(25)
}
@Test
@@ -866,18 +865,18 @@
testScope.runTest {
var notificationCount = 10
val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
- val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
+ val config by collectLastValue(underTest.getLockscreenDisplayConfig(calculateSpace))
advanceTimeBy(50L)
showLockscreen()
shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
- assertThat(maxNotifications).isEqualTo(10)
+ assertThat(config?.maxNotifications).isEqualTo(10)
// Shade expanding... still 10
shadeTestUtil.setLockscreenShadeExpansion(0.5f)
- assertThat(maxNotifications).isEqualTo(10)
+ assertThat(config?.maxNotifications).isEqualTo(10)
notificationCount = 25
@@ -885,20 +884,20 @@
shadeTestUtil.setLockscreenShadeTracking(true)
// Should still be 10, since the user is interacting
- assertThat(maxNotifications).isEqualTo(10)
+ assertThat(config?.maxNotifications).isEqualTo(10)
shadeTestUtil.setLockscreenShadeTracking(false)
shadeTestUtil.setLockscreenShadeExpansion(0f)
// Stopped tracking, show 25
- assertThat(maxNotifications).isEqualTo(25)
+ assertThat(config?.maxNotifications).isEqualTo(25)
}
@Test
fun maxNotificationsOnShade() =
testScope.runTest {
val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
- val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
+ val config by collectLastValue(underTest.getLockscreenDisplayConfig(calculateSpace))
advanceTimeBy(50L)
// Show lockscreen with shade expanded
@@ -908,7 +907,7 @@
configurationRepository.onAnyConfigurationChange()
// -1 means No Limit
- assertThat(maxNotifications).isEqualTo(-1)
+ assertThat(config?.maxNotifications).isEqualTo(-1)
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 7b91eae..38cfb9b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,8 @@
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.bouncer.domain.startable.BouncerStartable
import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.complication.ComplicationTypesUpdater
+import com.android.systemui.complication.DreamClockTimeComplication
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -337,4 +339,18 @@
abstract fun bindDreamOverlayRegistrant(
dreamOverlayRegistrant: DreamOverlayRegistrant
): CoreStartable
+
+ /** Inject into DreamClockTimeComplication.Registrant */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamClockTimeComplication.Registrant::class)
+ abstract fun bindDreamClockTimeComplicationRegistrant(
+ registrant: DreamClockTimeComplication.Registrant
+ ): CoreStartable
+
+ /** Inject into ComplicationTypesUpdater. */
+ @Binds
+ @IntoMap
+ @ClassKey(ComplicationTypesUpdater::class)
+ abstract fun bindComplicationTypesUpdater(updater: ComplicationTypesUpdater): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt
new file mode 100644
index 0000000..d7c3b6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
+import com.android.systemui.statusbar.disableflags.shared.model.DisableFlagsModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+class DisabledContentInteractor
+@Inject
+constructor(private val disableFlagsInteractor: DisableFlagsInteractor) {
+
+ /** Returns `true` if the given [key] is disabled; `false` if it's enabled */
+ fun isDisabled(
+ key: ContentKey,
+ disabledFlags: DisableFlagsModel = disableFlagsInteractor.disableFlags.value,
+ ): Boolean {
+ return with(disabledFlags) {
+ when (key) {
+ Scenes.Shade,
+ Overlays.NotificationsShade -> !isShadeEnabled()
+ Scenes.QuickSettings,
+ Overlays.QuickSettingsShade -> !isQuickSettingsEnabled()
+ else -> false
+ }
+ }
+ }
+
+ /** Runs the given [block] each time that [key] becomes disabled. */
+ suspend fun repeatWhenDisabled(key: ContentKey, block: suspend (disabled: ContentKey) -> Unit) {
+ disableFlagsInteractor.disableFlags
+ .map { isDisabled(key) }
+ .distinctUntilChanged()
+ .collectLatest { isDisabled ->
+ if (isDisabled) {
+ block(key)
+ }
+ }
+ }
+
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return combine(disableFlagsInteractor.disableFlags, unfiltered) {
+ disabledFlags,
+ unfilteredMap ->
+ unfilteredMap.filterValues { actionResult ->
+ val destination =
+ when (actionResult) {
+ is UserActionResult.ChangeScene -> actionResult.toScene
+ is UserActionResult.ShowOverlay -> actionResult.overlay
+ is UserActionResult.ReplaceByOverlay -> actionResult.overlay
+ else -> null
+ }
+ if (destination != null) {
+ // results that lead to a disabled destination get filtered out.
+ !isDisabled(key = destination, disabledFlags = disabledFlags)
+ } else {
+ // Action results that don't lead to a destination are never filtered out.
+ true
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index f20e5a5..d83d74e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -21,6 +21,8 @@
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
@@ -64,6 +66,7 @@
private val sceneFamilyResolvers: Lazy<Map<SceneKey, @JvmSuppressWildcards SceneResolver>>,
private val deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
private val keyguardEnabledInteractor: Lazy<KeyguardEnabledInteractor>,
+ private val disabledContentInteractor: DisabledContentInteractor,
) {
interface OnSceneAboutToChangeListener {
@@ -465,6 +468,10 @@
return false
}
+ if (disabledContentInteractor.isDisabled(to)) {
+ return false
+ }
+
val inMidTransitionFromGone =
(transitionState.value as? ObservableTransitionState.Transition)?.fromContent ==
Scenes.Gone
@@ -503,6 +510,10 @@
" Logging reason for overlay change was: $loggingReason"
}
+ if (to != null && disabledContentInteractor.isDisabled(to)) {
+ return false
+ }
+
val isFromValid = (from == null) || (from in currentOverlays.value)
val isToValid =
(to == null) || (to !in currentOverlays.value && to in repository.allContentKeys)
@@ -517,4 +528,14 @@
/** Returns `true` if [scene] can be resolved from [family]. */
fun isSceneInFamily(scene: SceneKey, family: SceneKey): Boolean =
sceneFamilyResolvers.get()[family]?.includesScene(scene) == true
+
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return disabledContentInteractor.filteredUserActions(unfiltered)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index aece5c6..8d8c24e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,7 +19,6 @@
package com.android.systemui.scene.domain.startable
import android.app.StatusBarManager
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
@@ -56,6 +55,7 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.interactor.DisabledContentInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -103,6 +103,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -144,6 +145,7 @@
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val vibratorHelper: VibratorHelper,
private val msdlPlayer: MSDLPlayer,
+ private val disabledContentInteractor: DisabledContentInteractor,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -281,6 +283,7 @@
handlePowerState()
handleDreamState()
handleShadeTouchability()
+ handleDisableFlags()
}
private fun handleBouncerImeVisibility() {
@@ -565,6 +568,38 @@
}
}
+ private fun handleDisableFlags() {
+ applicationScope.launch {
+ launch {
+ sceneInteractor.currentScene.collectLatest { currentScene ->
+ disabledContentInteractor.repeatWhenDisabled(currentScene) {
+ switchToScene(
+ targetSceneKey = SceneFamilies.Home,
+ loggingReason =
+ "Current scene ${currentScene.debugName} became" + " disabled",
+ )
+ }
+ }
+ }
+
+ launch {
+ sceneInteractor.currentOverlays.collectLatest { overlays ->
+ overlays.forEach { overlay ->
+ launch {
+ disabledContentInteractor.repeatWhenDisabled(overlay) {
+ sceneInteractor.hideOverlay(
+ overlay = overlay,
+ loggingReason =
+ "Overlay ${overlay.debugName} became" + " disabled",
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
private fun handleDeviceEntryHapticsWhileDeviceLocked() {
applicationScope.launch {
deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 32d5cb4..c1e8032 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -280,6 +280,16 @@
}
}
+ /**
+ * Returns a filtered version of [unfiltered], without action-result entries that would navigate
+ * to disabled scenes.
+ */
+ fun filteredUserActions(
+ unfiltered: Flow<Map<UserAction, UserActionResult>>
+ ): Flow<Map<UserAction, UserActionResult>> {
+ return sceneInteractor.filteredUserActions(unfiltered)
+ }
+
/** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
interface MotionEventHandler {
/** Notifies that a [MotionEvent] has occurred. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 4f73a345..bf2ea56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -17,9 +17,9 @@
package com.android.systemui.shade
import android.content.Context
+import android.content.MutableContextWrapper
import android.content.res.Resources
import android.view.LayoutInflater
-import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
@@ -29,7 +29,6 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -62,9 +61,7 @@
@SysUISingleton
fun provideShadeDisplayAwareContext(context: Context): Context {
return if (ShadeWindowGoesAround.isEnabled) {
- context
- .createWindowContext(context.display, TYPE_APPLICATION_OVERLAY, /* options= */ null)
- .apply { setTheme(R.style.Theme_SystemUI) }
+ MutableContextWrapper(context)
} else {
context
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 1055dcb..18dbba1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -16,8 +16,13 @@
package com.android.systemui.shade.domain.interactor
+import android.content.ComponentCallbacks
import android.content.Context
+import android.content.MutableContextWrapper
+import android.content.res.Configuration
+import android.content.res.Resources
import android.util.Log
+import android.view.WindowManager
import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
import com.android.app.tracing.coroutines.launchTraced
import com.android.app.tracing.traceSection
@@ -46,12 +51,17 @@
private val shadeRootView: WindowRootView,
private val shadePositionRepository: ShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: Context,
+ @ShadeDisplayAware private val shadeResources: Resources,
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
@Background private val bgScope: CoroutineScope,
- @ShadeDisplayAware private val configurationForwarder: ConfigurationForwarder,
- @Main private val mainContext: CoroutineContext,
+ @ShadeDisplayAware private val shadeConfigurationForwarder: ConfigurationForwarder,
+ @Main private val mainThreadContext: CoroutineContext,
) : CoreStartable {
+ // TODO: b/362719719 - Get rid of this callback as the root view should automatically get the
+ // correct configuration once it's moved to another window.
+ private var unregisterConfigChangedCallbacks: (() -> Unit)? = null
+
override fun start() {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
bgScope.launchTraced(TAG) {
@@ -60,43 +70,86 @@
}
/** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */
- private suspend fun moveShadeWindowTo(destinationDisplayId: Int) {
- val currentId = shadeRootView.display.displayId
- if (currentId == destinationDisplayId) {
+ private suspend fun moveShadeWindowTo(destinationId: Int) {
+ Log.d(TAG, "Trying to move shade window to display with id $destinationId")
+ val currentDisplay = shadeRootView.display
+ if (currentDisplay == null) {
+ Log.w(TAG, "Current shade display is null")
+ return
+ }
+ val currentId = currentDisplay.displayId
+ if (currentId == destinationId) {
Log.w(TAG, "Trying to move the shade to a display it was already in")
return
}
try {
- moveShadeWindow(fromId = currentId, toId = destinationDisplayId)
+ moveShadeWindow(fromId = currentId, toId = destinationId)
} catch (e: IllegalStateException) {
Log.e(
TAG,
- "Unable to move the shade window from display $currentId to $destinationDisplayId",
+ "Unable to move the shade window from display $currentId to $destinationId",
e,
)
}
}
private suspend fun moveShadeWindow(fromId: Int, toId: Int) {
- val sourceProperties = getDisplayWindowProperties(fromId)
- val destinationProperties = getDisplayWindowProperties(toId)
- traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
- withContext(mainContext) {
- traceSection("removeView") {
- sourceProperties.windowManager.removeView(shadeRootView)
- }
- traceSection("addView") {
- destinationProperties.windowManager.addView(
- shadeRootView,
- ShadeWindowLayoutParams.create(shadeContext),
- )
- }
+ val (_, _, _, sourceWm) = getDisplayWindowProperties(fromId)
+ val (_, _, destContext, destWm) = getDisplayWindowProperties(toId)
+ withContext(mainThreadContext) {
+ traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
+ removeShade(sourceWm)
+ addShade(destWm)
+ overrideContextAndResources(newContext = destContext)
+ registerConfigurationChange(destContext)
+ }
+ traceSection("ShadeDisplaysInteractor#onConfigurationChanged") {
+ dispatchConfigurationChanged(destContext.resources.configuration)
}
}
- traceSection("SecondaryShadeInteractor#onConfigurationChanged") {
- configurationForwarder.onConfigurationChanged(
- destinationProperties.context.resources.configuration
- )
+ }
+
+ private fun removeShade(wm: WindowManager): Unit =
+ traceSection("removeView") { wm.removeView(shadeRootView) }
+
+ private fun addShade(wm: WindowManager): Unit =
+ traceSection("addView") {
+ wm.addView(shadeRootView, ShadeWindowLayoutParams.create(shadeContext))
+ }
+
+ private fun overrideContextAndResources(newContext: Context) {
+ val contextWrapper =
+ shadeContext as? MutableContextWrapper
+ ?: error("Shade context is not a MutableContextWrapper!")
+ contextWrapper.baseContext = newContext
+ // Override needed in case someone is keeping a reference to the resources from the old
+ // context.
+ // TODO: b/362719719 - This shouldn't be needed, as resources should be updated when the
+ // window is moved to the new display automatically.
+ shadeResources.impl = shadeContext.resources.impl
+ }
+
+ private fun dispatchConfigurationChanged(newConfig: Configuration) {
+ shadeConfigurationForwarder.onConfigurationChanged(newConfig)
+ shadeRootView.dispatchConfigurationChanged(newConfig)
+ shadeRootView.requestLayout()
+ }
+
+ private fun registerConfigurationChange(context: Context) {
+ // we should keep only one at the time.
+ unregisterConfigChangedCallbacks?.invoke()
+ val callback =
+ object : ComponentCallbacks {
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ dispatchConfigurationChanged(newConfig)
+ }
+
+ override fun onLowMemory() {}
+ }
+ context.registerComponentCallbacks(callback)
+ unregisterConfigChangedCallbacks = {
+ context.unregisterComponentCallbacks(callback)
+ unregisterConfigChangedCallbacks = null
}
}
@@ -105,6 +158,6 @@
}
private companion object {
- const val TAG = "SecondaryShadeInteractor"
+ const val TAG = "ShadeDisplaysInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 45516aa..0d847d8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -23,6 +23,7 @@
import android.icu.text.DisplayContext
import android.os.UserHandle
import android.provider.Settings
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.ActivityStarter
@@ -48,7 +49,6 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Models UI state for the shade header. */
class ShadeHeaderViewModel
@@ -87,10 +87,6 @@
/** Whether or not the privacy chip is enabled in the device privacy config. */
val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
- private val _isDisabled = MutableStateFlow(false)
- /** Whether or not the Shade Header should be disabled based on disableFlags. */
- val isDisabled: StateFlow<Boolean> = _isDisabled.asStateFlow()
-
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
@@ -132,8 +128,6 @@
.collect { _mobileSubIds.value = it }
}
- launch { shadeInteractor.isQsEnabled.map { !it }.collect { _isDisabled.value = it } }
-
awaitCancellation()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
index 47a0429..733b986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -20,13 +20,21 @@
import android.app.Notification;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+import com.google.common.primitives.Booleans;
+
import javax.inject.Inject;
/**
@@ -44,12 +52,21 @@
@Override
public void attach(NotifPipeline pipeline) {
+ if (PromotedNotificationUi.isEnabled()) {
+ pipeline.addPromoter(mPromotedOngoingPromoter);
+ }
}
public NotifSectioner getSectioner() {
return mNotifSectioner;
}
+ private final NotifPromoter mPromotedOngoingPromoter = new NotifPromoter("PromotedOngoing") {
+ @Override
+ public boolean shouldPromoteToTopLevel(NotificationEntry child) {
+ return isPromotedOngoing(child);
+ }
+ };
/**
* Puts colorized foreground service and call notifications into its own section.
@@ -64,11 +81,30 @@
}
return false;
}
+
+ private NotifComparator mPreferPromoted = new NotifComparator("PreferPromoted") {
+ @Override
+ public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
+ return -1 * Booleans.compare(
+ isPromotedOngoing(o1.getRepresentativeEntry()),
+ isPromotedOngoing(o2.getRepresentativeEntry()));
+ }
+ };
+
+ @Nullable
+ @Override
+ public NotifComparator getComparator() {
+ if (PromotedNotificationUi.isEnabled()) {
+ return mPreferPromoted;
+ } else {
+ return null;
+ }
+ }
};
/** Determines if the given notification is a colorized or call notification */
public static boolean isRichOngoing(NotificationEntry entry) {
- return isColorizedForegroundService(entry) || isCall(entry);
+ return isPromotedOngoing(entry) || isColorizedForegroundService(entry) || isCall(entry);
}
private static boolean isColorizedForegroundService(NotificationEntry entry) {
@@ -78,6 +114,11 @@
&& entry.getImportance() > IMPORTANCE_MIN;
}
+ private static boolean isPromotedOngoing(NotificationEntry entry) {
+ // NOTE: isPromotedOngoing already checks the android.app.ui_rich_ongoing flag.
+ return entry != null && entry.getSbn().getNotification().isPromotedOngoing();
+ }
+
private static boolean isCall(NotificationEntry entry) {
Notification notification = entry.getSbn().getNotification();
return entry.getImportance() > IMPORTANCE_MIN
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 a90a105..fb62f80 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
@@ -1536,6 +1536,10 @@
return mPrivateLayout.getSingleLineView();
}
+ /**
+ * Whether this row is displayed over the unoccluded lockscreen. Returns false on the
+ * locked shade.
+ */
public boolean isOnKeyguard() {
return mOnKeyguard;
}
@@ -2820,7 +2824,8 @@
}
}
- void setOnKeyguard(boolean onKeyguard) {
+ /** @see #isOnKeyguard() */
+ public void setOnKeyguard(boolean onKeyguard) {
if (onKeyguard != mOnKeyguard) {
boolean wasAboveShelf = isAboveShelf();
final boolean wasExpanded = isExpanded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index baad616..ffe1b6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -41,6 +41,7 @@
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -378,15 +379,19 @@
mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
mPluginManager.addPluginListener(mView,
NotificationMenuRowPlugin.class, false /* Allow multiple */);
- mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
- mStatusBarStateController.addCallback(mStatusBarStateListener);
+ if (!SceneContainerFlag.isEnabled()) {
+ mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ }
mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
}
@Override
public void onViewDetachedFromWindow(View v) {
mPluginManager.removePluginListener(mView);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ if (!SceneContainerFlag.isEnabled()) {
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ }
mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index 5c9a0b9..f85545e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -100,6 +100,9 @@
*/
void addContainerViewAt(View v, int index);
+ /** Sets whether the notificatios are displayed on the unoccluded lockscreen. */
+ void setOnLockscreen(boolean isOnKeyguard);
+
/**
* Sets the maximum number of notifications to display.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 57af8ea..bddf6df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -575,6 +575,7 @@
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
private boolean mSuppressHeightUpdates;
+ private boolean mIsOnLockscreen;
/** Pass splitShadeStateController to view and update split shade */
public void passSplitShadeStateController(SplitShadeStateController splitShadeStateController) {
@@ -3228,9 +3229,12 @@
private void onViewAddedInternal(ExpandableView child) {
updateHideSensitiveForChild(child);
child.setOnHeightChangedListener(mOnChildHeightChangedListener);
- if (child instanceof ExpandableNotificationRow) {
+ if (child instanceof ExpandableNotificationRow row) {
NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
+ if (SceneContainerFlag.isEnabled()) {
+ row.setOnKeyguard(mIsOnLockscreen);
+ }
}
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
@@ -4752,8 +4756,11 @@
}
}
- void goToFullShade(long delay) {
- SceneContainerFlag.assertInLegacyMode();
+ /**
+ * Requests an animation for the next stack height update, to animate from the constrained stack
+ * displayed on the lock screen, to the scrollable stack displayed in the expanded shade.
+ */
+ public void animateGoToFullShade(long delay) {
mGoToFullShadeNeedsAnimation = true;
mGoToFullShadeDelay = delay;
mNeedsAnimation = true;
@@ -5356,12 +5363,38 @@
shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
}
+ /**
+ * Whether the notifications are displayed over the unoccluded lockscreen. Returns false on the
+ * locked shade.
+ */
+ public boolean isOnLockscreen() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return false;
+ return mIsOnLockscreen;
+ }
+
+ /** @see #isOnLockscreen() */
+ public void setOnLockscreen(boolean isOnLockscreen) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (mIsOnLockscreen != isOnLockscreen) {
+ mIsOnLockscreen = isOnLockscreen;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow childRow) {
+ childRow.setOnKeyguard(isOnLockscreen);
+ }
+ }
+ }
+ }
+
public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
mMaxDisplayedNotifications = maxDisplayedNotifications;
if (SceneContainerFlag.isEnabled()) {
updateIntrinsicStackHeight();
updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ if (maxDisplayedNotifications == -1) {
+ animateGoToFullShade(0);
+ }
} else {
updateContentHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index dc1a191..3d7501d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1222,7 +1222,7 @@
public void goToFullShade(long delay) {
SceneContainerFlag.assertInLegacyMode();
- mView.goToFullShade(delay);
+ mView.animateGoToFullShade(delay);
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
@@ -1603,6 +1603,12 @@
}
}
+ /** Sets whether the NSSL is displayed over the unoccluded Lockscreen. */
+ public void setOnLockscreen(boolean isOnLockscreen) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mNotificationListContainer.setOnLockscreen(isOnLockscreen);
+ }
+
/**
* Set the maximum number of notifications that can currently be displayed
*/
@@ -2029,6 +2035,11 @@
}
@Override
+ public void setOnLockscreen(boolean isOnLockscreen) {
+ mView.setOnLockscreen(isOnLockscreen);
+ }
+
+ @Override
public void setMaxDisplayedNotifications(int maxNotifications) {
mView.setMaxDisplayedNotifications(maxNotifications);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 4a55dfa..ea71460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -118,8 +118,12 @@
}
launch {
- viewModel.getMaxNotifications(calculateMaxNotifications).collect {
- controller.setMaxDisplayedNotifications(it)
+ viewModel.getLockscreenDisplayConfig(calculateMaxNotifications).collect {
+ (isOnLockscreen, maxNotifications) ->
+ if (SceneContainerFlag.isEnabled) {
+ controller.setOnLockscreen(isOnLockscreen)
+ }
+ controller.setMaxDisplayedNotifications(maxNotifications)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index fb60f26..a55a165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -718,9 +718,11 @@
* When expanding or when the user is interacting with the shade, keep the count stable; do not
* emit a value.
*/
- fun getMaxNotifications(calculateSpace: (Float, Boolean) -> Int): Flow<Int> {
+ fun getLockscreenDisplayConfig(
+ calculateSpace: (Float, Boolean) -> Int
+ ): Flow<LockscreenDisplayConfig> {
val showLimitedNotifications = isOnLockscreenWithoutShade
- val showUnlimitedNotifications =
+ val showUnlimitedNotificationsAndIsOnLockScreen =
combine(
isOnLockscreen,
keyguardInteractor.statusBarState,
@@ -730,28 +732,42 @@
)
.onStart { emit(false) },
) { isOnLockscreen, statusBarState, showAllNotifications ->
- statusBarState == SHADE_LOCKED || !isOnLockscreen || showAllNotifications
+ (statusBarState == SHADE_LOCKED || !isOnLockscreen || showAllNotifications) to
+ isOnLockscreen
}
+ @Suppress("UNCHECKED_CAST")
return combineTransform(
showLimitedNotifications,
- showUnlimitedNotifications,
+ showUnlimitedNotificationsAndIsOnLockScreen,
shadeInteractor.isUserInteracting,
availableHeight,
interactor.notificationStackChanged,
interactor.useExtraShelfSpace,
) { flows ->
val showLimitedNotifications = flows[0] as Boolean
- val showUnlimitedNotifications = flows[1] as Boolean
+ val (showUnlimitedNotifications, isOnLockscreen) =
+ flows[1] as Pair<Boolean, Boolean>
val isUserInteracting = flows[2] as Boolean
val availableHeight = flows[3] as Float
val useExtraShelfSpace = flows[5] as Boolean
if (!isUserInteracting) {
if (showLimitedNotifications) {
- emit(calculateSpace(availableHeight, useExtraShelfSpace))
+ emit(
+ LockscreenDisplayConfig(
+ isOnLockscreen = isOnLockscreen,
+ maxNotifications =
+ calculateSpace(availableHeight, useExtraShelfSpace),
+ )
+ )
} else if (showUnlimitedNotifications) {
- emit(-1)
+ emit(
+ LockscreenDisplayConfig(
+ isOnLockscreen = isOnLockscreen,
+ maxNotifications = -1,
+ )
+ )
}
}
}
@@ -775,9 +791,9 @@
SceneContainerFlag.assertInLegacyMode()
return combine(
- getMaxNotifications(calculateMaxNotifications).map {
- val height = calculateHeight(it)
- if (it == 0) {
+ getLockscreenDisplayConfig(calculateMaxNotifications).map { (_, maxNotifications) ->
+ val height = calculateHeight(maxNotifications)
+ if (maxNotifications == 0) {
height - shelfHeight
} else {
height
@@ -815,4 +831,13 @@
*/
data class FloatAtEnd(val width: Int) : HorizontalPosition
}
+
+ /**
+ * Data class representing a configuration for displaying Notifications on the Lockscreen.
+ *
+ * @param isOnLockscreen is the user on the lockscreen
+ * @param maxNotifications Limit for the max number of top-level Notifications to be displayed.
+ * A value of -1 indicates no limit.
+ */
+ data class LockscreenDisplayConfig(val isOnLockscreen: Boolean, val maxNotifications: Int)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 72cb1df..1556058 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,12 @@
package com.android.systemui.kosmos
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
+import com.android.systemui.util.mockito.mock
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -32,12 +36,15 @@
fun Kosmos.useUnconfinedTestDispatcher() = apply { testDispatcher = UnconfinedTestDispatcher() }
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
-var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
+var Kosmos.backgroundScope by Fixture { testScope.backgroundScope }
+var Kosmos.applicationCoroutineScope by Fixture { backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
- testScope.backgroundScope.coroutineContext
+ backgroundScope.coroutineContext
}
var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.brightnessWarningToast: BrightnessWarningToast by
+ Kosmos.Fixture { mock<BrightnessWarningToast>() }
/**
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
@@ -49,3 +56,5 @@
fun Kosmos.runCurrent() = testScope.runCurrent()
fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
+
+fun <T> Kosmos.collectValues(flow: Flow<T>): FlowValue<List<T>> = testScope.collectValues(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt
new file mode 100644
index 0000000..12d4e90
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/DisabledContentInteractorKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
+
+val Kosmos.disabledContentInteractor by Fixture {
+ DisabledContentInteractor(disableFlagsInteractor = disableFlagsInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index f84c3bd..eb352ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -33,5 +33,6 @@
sceneFamilyResolvers = { sceneFamilyResolvers },
deviceUnlockedInteractor = { deviceUnlockedInteractor },
keyguardEnabledInteractor = { keyguardEnabledInteractor },
+ disabledContentInteractor = disabledContentInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 7e6a727..82b5f63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -37,6 +37,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.disabledContentInteractor
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -83,5 +84,6 @@
alternateBouncerInteractor = alternateBouncerInteractor,
vibratorHelper = vibratorHelper,
msdlPlayer = msdlPlayer,
+ disabledContentInteractor = disabledContentInteractor,
)
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 2808056..a0b989b 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -55,16 +55,6 @@
}
flag {
- name: "compute_window_changes_on_a11y_v2"
- namespace: "accessibility"
- description: "Computes accessibility window changes in accessibility instead of wm package."
- bug: "322444245"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "deprecate_package_list_observer"
namespace: "accessibility"
description: "Stops using the deprecated PackageListObserver."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 9a81aa6..5cbe0c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -433,22 +433,10 @@
return Collections.emptyList();
}
- /**
- * Callbacks from window manager when there's an accessibility change in windows.
- *
- * @param forceSend Send the windows for accessibility even if they haven't changed.
- * @param topFocusedDisplayId The display Id which has the top focused window.
- * @param topFocusedWindowToken The window token of top focused window.
- * @param windows The windows for accessibility.
- */
- @Override
- public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
+ private void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
+ // TODO(b/322444245): no longer need to get a lock.
synchronized (mLock) {
- if (!Flags.computeWindowChangesOnA11yV2()) {
- // If the flag is enabled, it's already done in #createWindowInfoListLocked.
- updateWindowsByWindowAttributesLocked(windows);
- }
if (DEBUG) {
Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+ "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
@@ -490,9 +478,7 @@
}
/**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * true.
+ * Called when the windows for accessibility changed.
*
* @param forceSend Send the windows for accessibility even if they haven't
* changed.
@@ -655,16 +641,6 @@
return true;
}
- private void updateWindowsByWindowAttributesLocked(List<WindowInfo> windows) {
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowInfo windowInfo = windows.get(i);
- final IBinder token = windowInfo.token;
- final int windowId = findWindowIdLocked(
- mAccessibilityUserManager.getCurrentUserIdLocked(), token);
- updateWindowWithWindowAttributes(windowInfo, mWindowAttributes.get(windowId));
- }
- }
-
private void updateWindowWithWindowAttributes(@NonNull WindowInfo windowInfo,
@Nullable AccessibilityWindowAttributes attributes) {
if (attributes == null) {
@@ -990,19 +966,6 @@
private AccessibilityWindowInfo populateReportedWindowLocked(int userId,
WindowInfo window, SparseArray<AccessibilityWindowInfo> oldWindowsById) {
final int windowId = findWindowIdLocked(userId, window.token);
-
- // With the flag enabled, createWindowInfoListLocked() already removes invalid windows.
- if (!Flags.computeWindowChangesOnA11yV2()) {
- if (windowId < 0) {
- return null;
- }
-
- // Don't need to add the embedded hierarchy windows into the a11y windows list.
- if (isEmbeddedHierarchyWindowsLocked(windowId)) {
- return null;
- }
- }
-
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
reportedWindow.setId(windowId);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 415f78a..b7a5f3e 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -598,7 +598,7 @@
}
if (r != null) {
- mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
+ mPackageWatchdog.notifyPackageFailure(r.getPackageListWithVersionCode(),
PackageWatchdog.FAILURE_REASON_APP_CRASH);
synchronized (mService) {
@@ -1142,7 +1142,7 @@
}
// Notify PackageWatchdog without the lock held
if (packageList != null) {
- mPackageWatchdog.onPackageFailure(packageList,
+ mPackageWatchdog.notifyPackageFailure(packageList,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2eb9f3c..d5bd057 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1262,6 +1262,17 @@
mFrameworkStatsLogger = frameworkStatsLogger;
}
+ private static float clampPowerMah(double powerMah, String consumer) {
+ float resultPowerMah = 0;
+ if (powerMah <= Float.MAX_VALUE && powerMah >= Float.MIN_VALUE) {
+ resultPowerMah = (float) powerMah;
+ } else {
+ // Handle overflow appropriately
+ Slog.wtfStack(TAG, consumer + " reported powerMah float overflow: " + powerMah);
+ }
+ return resultPowerMah;
+ }
+
/**
* Generates StatsEvents for the supplied battery usage stats and adds them to
* the supplied list.
@@ -1282,7 +1293,8 @@
bus.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+ final float totalDeviceConsumedPowerMah =
+ clampPowerMah(deviceConsumer.getConsumedPower(), "AggregateBatteryConsumer");
for (@BatteryConsumer.PowerComponentId int powerComponentId :
deviceConsumer.getPowerComponentIds()) {
@@ -1314,7 +1326,9 @@
// Log single atom for BatteryUsageStats per uid/process_state/component/etc.
for (UidBatteryConsumer uidConsumer : uidConsumers) {
final int uid = uidConsumer.getUid();
- final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+ final float totalConsumedPowerMah =
+ clampPowerMah(uidConsumer.getConsumedPower(), "uidConsumer-" + uid);
for (@BatteryConsumer.PowerComponentId int powerComponentId :
uidConsumer.getPowerComponentIds()) {
@@ -1358,7 +1372,10 @@
}
final String powerComponentName = batteryConsumer.getPowerComponentName(componentId);
- final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final double consumedPowerMah = batteryConsumer.getConsumedPower(key);
+ float powerMah =
+ clampPowerMah(
+ consumedPowerMah, "uidConsumer-" + uid + "-" + powerComponentName);
final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
if (powerMah == 0 && powerComponentDurationMillis == 0) {
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ed41f2e..fa2e674 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -31,7 +31,6 @@
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
@@ -177,8 +176,6 @@
case OP_RECORD_AUDIO:
case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
return PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
- case OP_TAKE_AUDIO_FOCUS:
- return PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
default:
return PROCESS_CAPABILITY_NONE;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 40d5f86..5c2eb5c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4954,6 +4954,15 @@
}
final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
+
+ final Set<Integer> a2dpDevices = AudioSystem.intersectionAudioDeviceTypes(
+ AudioSystem.DEVICE_OUT_ALL_A2DP_SET, deviceTypes);
+ if (!a2dpDevices.isEmpty()) {
+ int index = getStreamVolume(streamType,
+ a2dpDevices.toArray(new Integer[0])[0].intValue());
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
+ }
+
final Set<Integer> absVolumeMultiModeCaseDevices =
AudioSystem.intersectionAudioDeviceTypes(
mAbsVolumeMultiModeCaseDevices, deviceTypes);
@@ -11425,6 +11434,10 @@
return mSpatializerHelper.canBeSpatialized(attributes, format);
}
+ public @NonNull List<Integer> getSpatializedChannelMasks() {
+ return mSpatializerHelper.getSpatializedChannelMasks();
+ }
+
/** @see Spatializer.SpatializerInfoDispatcherStub */
public void registerSpatializerCallback(
@NonNull ISpatializerCallback cb) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 9265ff2..afa90d5 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -59,6 +59,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -1100,6 +1102,23 @@
return able;
}
+ synchronized @NonNull List<Integer> getSpatializedChannelMasks() {
+ if (!checkSpatializer("getSpatializedChannelMasks")) {
+ return Collections.emptyList();
+ }
+ try {
+ final int[] nativeMasks = new int[0]; // FIXME mSpat query goes here
+ for (int i = 0; i < nativeMasks.length; i++) {
+ nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
+ }
+ final List<Integer> masks = Arrays.stream(nativeMasks).boxed().toList();
+ return masks;
+ } catch (Exception e) { // just catch Exception in case nativeMasks is null
+ Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
+ return Collections.emptyList();
+ }
+ }
+
//------------------------------------------------------
// head tracking
final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks =
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
index 8e72546..eef2b15 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -93,7 +93,8 @@
return;
}
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ PackageWatchdog.getInstance(mContext).notifyPackageFailure(pkgList,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
});
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 7cbacd6..4b7e74a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -1784,22 +1783,13 @@
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
- windows = buildWindowInfoListLocked(visibleWindows, screenSize);
- }
-
// Gets the top focused display Id and window token for supporting multi-display.
topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
- mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, screenSize, visibleWindows);
- } else {
- mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, windows);
- }
+ mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
+ topFocusedWindowToken, screenSize, visibleWindows);
// Recycle the windows as we do not need them.
for (final AccessibilityWindowsPopulator.AccessibilityWindow window : visibleWindows) {
@@ -1808,166 +1798,6 @@
mInitialized = true;
}
- // Here are old code paths, called when computeWindowChangesOnA11yV2 flag is disabled.
- // LINT.IfChange
-
- /**
- * From a list of windows, decides windows to be exposed to accessibility based on touchable
- * region in the screen.
- */
- private List<WindowInfo> buildWindowInfoListLocked(List<AccessibilityWindow> visibleWindows,
- Point screenSize) {
- final List<WindowInfo> windows = new ArrayList<>();
- final Set<IBinder> addedWindows = mTempBinderSet;
- addedWindows.clear();
-
- boolean focusedWindowAdded = false;
-
- final int visibleWindowCount = visibleWindows.size();
-
- Region unaccountedSpace = mTempRegion;
- unaccountedSpace.set(0, 0, screenSize.x, screenSize.y);
-
- // Iterate until we figure out what is touchable for the entire screen.
- for (int i = 0; i < visibleWindowCount; i++) {
- final AccessibilityWindow a11yWindow = visibleWindows.get(i);
- final Region regionInWindow = new Region();
- a11yWindow.getTouchableRegionInWindow(regionInWindow);
- if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) {
- addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
- if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
- updateUnaccountedSpace(a11yWindow, unaccountedSpace);
- }
- focusedWindowAdded |= a11yWindow.isFocused();
- } else if (a11yWindow.isUntouchableNavigationBar()) {
- // If this widow is navigation bar without touchable region, accounting the
- // region of navigation bar inset because all touch events from this region
- // would be received by launcher, i.e. this region is a un-touchable one
- // for the application.
- unaccountedSpace.op(
- getSystemBarInsetsFrame(
- mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
- unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- }
-
- if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
- break;
- }
- }
-
- // Remove child/parent references to windows that were not added.
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- WindowInfo window = windows.get(i);
- if (!addedWindows.contains(window.parentToken)) {
- window.parentToken = null;
- }
- if (window.childTokens != null) {
- final int childTokenCount = window.childTokens.size();
- for (int j = childTokenCount - 1; j >= 0; j--) {
- if (!addedWindows.contains(window.childTokens.get(j))) {
- window.childTokens.remove(j);
- }
- }
- // Leave the child token list if empty.
- }
- }
-
- addedWindows.clear();
-
- return windows;
- }
-
- // Some windows should be excluded from unaccounted space computation, though they still
- // should be reported
- private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
- // Do not account space of trusted non-touchable windows, except the split-screen
- // divider.
- // If it's not trusted, touch events are not sent to the windows behind it.
- if (!a11yWindow.isTouchable()
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
- && a11yWindow.isTrustedOverlay()) {
- return false;
- }
-
- if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
- return false;
- }
- return true;
- }
-
- private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
- Region regionInScreen, Region unaccountedSpace) {
- if (a11yWindow.isFocused()) {
- return true;
- }
-
- // Ignore non-touchable windows, except the split-screen divider, which is
- // occasionally non-touchable but still useful for identifying split-screen
- // mode and the PIP menu.
- if (!a11yWindow.isTouchable()
- && (a11yWindow.getType() != TYPE_DOCK_DIVIDER
- && !a11yWindow.isPIPMenu())) {
- return false;
- }
-
- // If the window is completely covered by other windows - ignore.
- if (unaccountedSpace.quickReject(regionInScreen)) {
- return false;
- }
-
- // Add windows of certain types not covered by modal windows.
- if (isReportedWindowType(a11yWindow.getType())) {
- return true;
- }
-
- return false;
- }
-
- private void updateUnaccountedSpace(AccessibilityWindow a11yWindow,
- Region unaccountedSpace) {
- if (a11yWindow.getType()
- != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
- // Account for the space this window takes if the window
- // is not an accessibility overlay which does not change
- // the reported windows.
- final Region touchableRegion = mTempRegion2;
- a11yWindow.getTouchableRegionInScreen(touchableRegion);
- unaccountedSpace.op(touchableRegion, unaccountedSpace,
- Region.Op.REVERSE_DIFFERENCE);
- }
- }
-
- private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
- Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
- final WindowInfo window = a11yWindow.getWindowInfo();
- if (window.token == null) {
- // The window was used in calculating visible windows but does not have an
- // associated IWindow token, so exclude it from the list returned to accessibility.
- return;
- }
- window.regionInScreen.set(regionInScreen);
- window.layer = tokenOut.size();
- out.add(window);
- tokenOut.add(window.token);
- }
-
- private static boolean isReportedWindowType(int windowType) {
- return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
- && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
- && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_DRAG
- && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
- && windowType != WindowManager.LayoutParams.TYPE_POINTER
- && windowType != TYPE_MAGNIFICATION_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
- && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
- }
-
- // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java)
-
private WindowState getTopFocusWindow() {
return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index fd2a909..7fc11e6 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -724,8 +724,7 @@
}
// Compute system bar insets frame if needed.
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()
- && windowState != null && instance.isUntouchableNavigationBar()) {
+ if (windowState != null && instance.isUntouchableNavigationBar()) {
final InsetsSourceProvider provider =
windowState.getControllableInsetProvider();
if (provider != null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index ce032b4..c77b1d9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -46,7 +46,6 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
-import android.view.WindowInfo;
import android.view.WindowManager.DisplayImePolicy;
import android.view.inputmethod.ImeTracker;
import android.window.ScreenCapture;
@@ -158,26 +157,8 @@
* accessibility changed.
*/
public interface WindowsForAccessibilityCallback {
-
/**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * false.
- *
- * @param forceSend Send the windows for accessibility even if they haven't changed.
- * @param topFocusedDisplayId The display Id which has the top focused window.
- * @param topFocusedWindowToken The window token of top focused window.
- * @param windows The windows for accessibility.
- */
- void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
- IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
-
- /**
- * Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
- * true.
- * TODO(b/322444245): Remove screenSize parameter by getting it from
- * DisplayManager#getDisplay(int).getRealSize() on the a11y side.
+ * Called when the windows for accessibility changed.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
* @param topFocusedDisplayId The display Id which has the top focused window.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 403930d..2ae31ad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -18,20 +18,24 @@
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.EventWindowIdMatcher.eventWindowId;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.windowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,14 +46,13 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
@@ -63,6 +66,7 @@
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -70,7 +74,6 @@
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -81,17 +84,9 @@
import java.util.Arrays;
import java.util.List;
-// This test verifies deprecated codepath. Probably changing this file means
-// AccessibilityWindowManagerWithAccessibilityWindowTest also needs to be updated.
-// LINT.IfChange
-
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
- * enabled.
- * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
- * after completing the flag migration.
+ * Tests for the AccessibilityWindowManager.
*/
-@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
@@ -122,9 +117,8 @@
// List of window token, mapping from windowId -> window token.
private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> window info lists.
- private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
- new SparseArray<>();
+ // List of window info lists, mapping from displayId -> a11y window lists.
+ private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
// List of callback, mapping from displayId -> callback.
private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
new SparseArray<>();
@@ -134,6 +128,13 @@
private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ // This maps displayId -> next region offset.
+ // Touchable region must have un-occluded area so that it's exposed to a11y services.
+ // This offset can be used as left and top of new region so that top-left of each region are
+ // kept visible.
+ // It's expected to be incremented by some amount everytime the value is used.
+ private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
@Mock private WindowManagerInternal mMockWindowManagerInternal;
@Mock private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
@Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
@@ -144,9 +145,6 @@
@Mock private IBinder mMockEmbeddedToken;
@Mock private IBinder mMockInvalidToken;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -159,7 +157,7 @@
anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
doAnswer((invocation) -> {
- onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ onAccessibilityWindowsChanged(invocation.getArgument(0), false);
return null;
}).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
@@ -173,7 +171,7 @@
// as top focused display before each testing starts.
startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
- // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
@@ -237,19 +235,18 @@
@Test
public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo focusedWindowInfo =
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, focusedWindowInfo.token));
focusedWindowInfo.focused = false;
- focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
- focusedWindowInfo.focused = true;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
mA11yWindowManager.onTouchInteractionStart();
setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
}
@@ -273,7 +270,7 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should not be changed.
assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should not be changed.
@@ -301,8 +298,8 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should be changed.
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should be changed.
@@ -312,53 +309,181 @@
@Test
public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
- assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
is(a11yWindow.getLayer()));
}
}
@Test
public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
final IBinder windowToken = mA11yWindowManager
.getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(i).getWindowInfo();
assertThat(windowToken, is(windowInfo.token));
}
}
@Test
- public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertNotEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
}
@Test
- public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
+ public void onWindowsChanged_embeddedWindows_shouldOnlyReportHost() throws RemoteException {
+ final Rect embeddingBounds = new Rect(0, 0, 200, 100);
+
+ // The embedded window comes front of the host window.
+ final IBinder embeddedWindowLeashToken = Mockito.mock(IBinder.class);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, embeddedWindowLeashToken, USER_SYSTEM_ID);
+ final AccessibilityWindow embeddedWindow = createMockAccessibilityWindow(
+ mA11yWindowTokens.get(embeddedWindowId), Display.DEFAULT_DISPLAY);
+ setRegionForMockAccessibilityWindow(embeddedWindow, new Region(embeddingBounds));
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0, embeddedWindow);
+
+ final IBinder hostWindowLeashToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, hostWindowLeashToken, USER_SYSTEM_ID);
+ final AccessibilityWindow hostWindow = createMockAccessibilityWindow(
+ mA11yWindowTokens.get(hostWindowId), Display.DEFAULT_DISPLAY);
+ setRegionForMockAccessibilityWindow(hostWindow, new Region(embeddingBounds));
+ mWindows.get(Display.DEFAULT_DISPLAY).set(1, hostWindow);
+
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(
+ hostWindowLeashToken, embeddedWindowLeashToken);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(embeddedWindowId))));
+ assertThat(a11yWindows.get(0), windowId(hostWindowId));
+ final Rect bounds = new Rect();
+ a11yWindows.get(0).getBoundsInScreen(bounds);
+ assertEquals(bounds, embeddingBounds);
+ }
+
+ @Test
+ public void onWindowsChanged_shouldNotReportfullyOccludedWindow() {
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow, new Region(100, 100, 300, 300));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ // index 1 is focused. Let's use the next one for this test.
+ final AccessibilityWindow occludedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(2);
+ setRegionForMockAccessibilityWindow(occludedWindow, new Region(150, 150, 250, 250));
+ final int occludedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, occludedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(frontWindowId)));
+ assertThat(a11yWindows, not(hasItem(windowId(occludedWindowId))));
+ }
+
+ @Test
+ public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+ assertNotEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
+
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
}
@Test
@@ -368,14 +493,10 @@
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
true, USER_SYSTEM_ID);
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = token.asBinder();
- windowInfo.layer = 0;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+ createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(oldWindow,
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
}
@@ -383,12 +504,12 @@
@Test
public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
final WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
focusedWindowInfo.focused = false;
windowInfo.focused = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
.isFocused());
}
@@ -497,15 +618,18 @@
@Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
- windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
- windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(0).getId();
@@ -523,12 +647,17 @@
@Test
public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
// Updates z-order #1 WindowInfo is half visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -539,9 +668,17 @@
@Test
public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ // Note that the second window is also exposed even if region is empty because it's focused.
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -552,16 +689,21 @@
@Test
public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
// Updates z-order #0 WindowInfo to have two interact-able areas.
- Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(region);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow, region);
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
+ final int windowId = a11yWindows.get(1).getId();
mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
assertFalse(outBounds.getBounds().isEmpty());
@@ -572,7 +714,8 @@
@Test
public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
final IBinder eventWindowToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, eventWindowToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -611,11 +754,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -641,7 +784,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -690,12 +833,12 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -722,7 +865,7 @@
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
noUse);
assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
}
@@ -751,11 +894,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -763,7 +906,8 @@
public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
throws RemoteException {
final IBinder defaultFocusWinToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, defaultFocusWinToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -808,8 +952,8 @@
@Test
public void getPictureInPictureWindow_shouldNotNull() {
assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
}
@@ -823,8 +967,9 @@
final IAccessibilityInteractionConnection mockRemoteConnection =
mA11yWindowManager.getConnectionLocked(
USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+ true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
verify(mockRemoteConnection).notifyOutsideTouch();
@@ -942,18 +1087,14 @@
@Test
public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
// Removing index 0 because it's not focused, and avoids unnecessary layer change.
final int windowId =
getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- infos.remove(0);
- for (WindowInfo info : infos) {
- // Adjust layer number because it should start from 0.
- info.layer--;
- }
+ windows.remove(0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -961,27 +1102,21 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@Test
public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
-
- for (WindowInfo info : infos) {
- // Adjust layer number because new window will have 0 so that layer number in
- // A11yWindowInfo in window won't be changed.
- info.layer++;
- }
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
false, USER_SYSTEM_ID);
- addWindowInfo(infos, token, 0);
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+ // Adding window to the front so that other windows' layer won't change.
+ windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -989,17 +1124,17 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@Test
public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
- infos.get(0).title = "new title";
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+ windows.get(0).getWindowInfo().title = "new title";
final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -1007,7 +1142,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1017,48 +1152,47 @@
}
private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
// mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
// for the test.
- int layer = 0;
for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
true, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
for (int i = 0; i < NUM_APP_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
false, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
// Sets up current focused window of display.
// Each display has its own current focused window if config_perDisplayFocusEnabled is true.
// Otherwise only default display needs to current focused window.
if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
}
// Turns on windows tracking, and update window info.
mA11yWindowManager.startTrackingWindows(displayId, false);
// Puts window lists into array.
- mWindowInfos.put(displayId, windowInfosForDisplay);
+ mWindows.put(displayId, windowsForDisplay);
// Sets the default display is the top focused display and
// its current focused window is the top focused window.
if (displayId == Display.DEFAULT_DISPLAY) {
setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
}
// Invokes callback for sending window lists to A11y framework.
- onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+ onAccessibilityWindowsChanged(displayId, FORCE_SEND);
assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowInfosForDisplay.size());
+ windowsForDisplay.size());
}
private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
ArgumentCaptor.forClass(
- WindowManagerInternal.WindowsForAccessibilityCallback.class);
+ WindowsForAccessibilityCallback.class);
verify(mMockWindowManagerInternal)
.setWindowsForAccessibilityCallback(eq(displayId),
windowsForAccessibilityCallbacksCaptor.capture());
@@ -1106,36 +1240,28 @@
return windowId;
}
- private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
- windowInfo.layer = layer;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- windowInfos.add(windowInfo);
- }
-
private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
return mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, windowToken);
}
private void setTopFocusedWindowAndDisplay(int displayId, int index) {
// Sets the top focus window.
- mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
// Sets the top focused display.
mTopFocusedDisplayId = displayId;
}
- private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
if (callbacks == null) {
callbacks = getWindowsForAccessibilityCallbacks(displayId);
mCallbackOfWindows.put(displayId, callbacks);
}
- callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+ mWindows.get(displayId));
}
private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
@@ -1144,23 +1270,23 @@
if (mSupportPerDisplayFocus) {
// Gets the old focused window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Gets the new window of display which wants to change focused window.
focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
} else {
// Gets the window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
// Gets the old focused window of old top focused display.
focusedWindowInfo =
- mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Changes the top focused display and window.
@@ -1168,6 +1294,39 @@
}
}
+ private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+
+ final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+ when(window.getWindowInfo()).thenReturn(windowInfo);
+ when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+ when(window.isTouchable()).thenReturn(true);
+ when(window.getType()).thenReturn(windowInfo.type);
+
+ setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+ return window;
+ }
+
+ private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInScreen(any(Region.class));
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInWindow(any(Region.class));
+ }
+
+ private Region nextToucableRegion(int displayId) {
+ final int topLeft = mNextRegionOffsets.get(displayId, 0);
+ final int bottomRight = topLeft + 100;
+ mNextRegionOffsets.put(displayId, topLeft + 10);
+ return new Region(topLeft, topLeft, bottomRight, bottomRight);
+ }
+
@Nullable
private static String toString(@Nullable CharSequence cs) {
return cs == null ? null : cs.toString();
@@ -1196,16 +1355,16 @@
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1241,5 +1400,27 @@
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
-// LINT.ThenChange(/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
deleted file mode 100644
index 1904145..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ /dev/null
@@ -1,1444 +0,0 @@
-/*
- * Copyright (C) 2019 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.accessibility;
-
-import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-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.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.LocaleList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.IWindow;
-import android.view.WindowInfo;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowAttributes;
-import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.accessibility.IAccessibilityInteractionConnection;
-
-import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
-import com.android.server.accessibility.test.MessageCapturingHandler;
-import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
-import com.android.server.wm.WindowManagerInternal;
-import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
- * TODO(b/322444245): Merge with AccessibilityWindowManagerTest
- * after completing the flag migration.
- */
-@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
-public class AccessibilityWindowManagerWithAccessibilityWindowTest {
- private static final String PACKAGE_NAME = "com.android.server.accessibility";
- private static final boolean FORCE_SEND = true;
- private static final boolean SEND_ON_WINDOW_CHANGES = false;
- private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
- private static final int USER_PROFILE = 11;
- private static final int USER_PROFILE_PARENT = 1;
- private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
- private static final int NUM_GLOBAL_WINDOWS = 4;
- private static final int NUM_APP_WINDOWS = 4;
- private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS);
- private static final int DEFAULT_FOCUSED_INDEX = 1;
- private static final int SCREEN_WIDTH = 1080;
- private static final int SCREEN_HEIGHT = 1920;
- private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
- private static final int HOST_WINDOW_ID = 10;
- private static final int EMBEDDED_WINDOW_ID = 11;
- private static final int OTHER_WINDOW_ID = 12;
-
- private AccessibilityWindowManager mA11yWindowManager;
- // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true,
- // i.e., each display would have its current focused window, and one of all focused windows
- // would be top focused window. Otherwise, window manager only supports one focused window
- // at all displays, and that focused window would be top focused window.
- private boolean mSupportPerDisplayFocus = false;
- private int mTopFocusedDisplayId = Display.INVALID_DISPLAY;
- private IBinder mTopFocusedWindowToken = null;
-
- // List of window token, mapping from windowId -> window token.
- private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> a11y window lists.
- private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
- // List of callback, mapping from displayId -> callback.
- private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
- new SparseArray<>();
- // List of display ID.
- private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList(
- Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID));
-
- private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
-
- // This maps displayId -> next region offset.
- // Touchable region must have un-occluded area so that it's exposed to a11y services.
- // This offset can be used as left and top of new region so that top-left of each region are
- // kept visible.
- // It's expected to be incremented by some amount everytime the value is used.
- private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
-
- @Mock
- private WindowManagerInternal mMockWindowManagerInternal;
- @Mock
- private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
- @Mock
- private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
- @Mock
- private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
- @Mock
- private AccessibilityTraceManager mMockA11yTraceManager;
-
- @Mock
- private IBinder mMockHostToken;
- @Mock
- private IBinder mMockEmbeddedToken;
- @Mock
- private IBinder mMockInvalidToken;
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Before
- public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
- when(mMockA11yUserManager.getCurrentUserIdLocked()).thenReturn(USER_SYSTEM_ID);
- when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
- USER_PROFILE)).thenReturn(USER_PROFILE_PARENT);
- when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
- USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID);
- when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked(
- anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
-
- doAnswer((invocation) -> {
- onAccessibilityWindowsChanged(invocation.getArgument(0), false);
- return null;
- }).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
-
- mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler,
- mMockWindowManagerInternal,
- mMockA11yEventSender,
- mMockA11ySecurityPolicy,
- mMockA11yUserManager,
- mMockA11yTraceManager);
- // Starts tracking window of default display and sets the default display
- // as top focused display before each testing starts.
- startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
-
- // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
- // Resets it for mockito verify of further test case.
- Mockito.reset(mMockA11yEventSender);
-
- registerLeashedTokenAndWindowId();
- }
-
- @After
- public void tearDown() {
- mHandler.removeAllMessages();
- }
-
- @Test
- public void startTrackingWindows_shouldEnableWindowManagerCallback() {
- // AccessibilityWindowManager#startTrackingWindows already invoked in setup.
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- final WindowsForAccessibilityCallback callbacks =
- mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
- verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
- eq(Display.DEFAULT_DISPLAY), eq(callbacks));
- }
-
- @Test
- public void stopTrackingWindows_shouldDisableWindowManagerCallback() {
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- Mockito.reset(mMockWindowManagerInternal);
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
- eq(Display.DEFAULT_DISPLAY), isNull());
-
- }
-
- @Test
- public void stopTrackingWindows_shouldClearWindows() {
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY));
- assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
- assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID),
- activeWindowId);
- }
-
- @Test
- public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
- // Stops tracking windows of second display.
- mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID);
- assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
- }
-
- @Test
- public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- final WindowInfo focusedWindowInfo =
- mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
- assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, focusedWindowInfo.token));
-
- focusedWindowInfo.focused = false;
- mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
-
- mA11yWindowManager.onTouchInteractionStart();
- setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- }
-
- @Test
- public void
- onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true.
- mSupportPerDisplayFocus = true;
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- // Gets the active window.
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- // Gets the top focused window.
- final int topFocusedWindowId =
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
- // Changes the current focused window at second display.
- changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
- DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
-
- onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
- // The active window should not be changed.
- assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- // The top focused window should not be changed.
- assertEquals(topFocusedWindowId,
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
- }
-
- @Test
- public void
- onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow()
- throws RemoteException {
- // At setup, the default display sets be the top focused display and
- // its current focused window sets be the top focused window.
- // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is
- // false.
- mSupportPerDisplayFocus = false;
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- // Gets the active window.
- final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- // Gets the top focused window.
- final int topFocusedWindowId =
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
- // Changes the current focused window from default display to second display.
- changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
- DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
- // The active window should be changed.
- assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
- // The top focused window should be changed.
- assertNotEquals(topFocusedWindowId,
- mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
- }
-
- @Test
- public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
- List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < a11yWindows.size(); i++) {
- final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
- is(a11yWindow.getLayer()));
- }
- }
-
- @Test
- public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
- List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < a11yWindows.size(); i++) {
- final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final IBinder windowToken = mA11yWindowManager
- .getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
- .get(i).getWindowInfo();
- assertThat(windowToken, is(windowInfo.token));
- }
- }
-
- @Test
- public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- when(window.isTouchable()).thenReturn(false);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, window.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, not(hasItem(windowId(windowId))));
- }
-
- @Test
- public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- when(window.isTouchable()).thenReturn(false);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, window.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasItem(windowId(windowId)));
- }
-
- @Test
- public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
- // Make the focused trusted un-touchable window fullscreen.
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- when(window.isTouchable()).thenReturn(false);
- when(window.isTrustedOverlay()).thenReturn(true);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
- }
-
- @Test
- public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
- // Make the a11y overlay window fullscreen.
- final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
- }
-
- @Test
- public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
- // Make the front window fullscreen.
- final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(frontWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
-
- final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX);
- final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- assertThat(a11yWindows.get(0), windowId(frontWindowId));
- assertThat(a11yWindows.get(1), windowId(focusedWindowId));
- }
-
- @Test
- public void onWindowsChanged_embeddedWindows_shouldOnlyReportHost() throws RemoteException {
- final Rect embeddingBounds = new Rect(0, 0, 200, 100);
-
- // The embedded window comes front of the host window.
- final IBinder embeddedWindowLeashToken = Mockito.mock(IBinder.class);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, embeddedWindowLeashToken, USER_SYSTEM_ID);
- final AccessibilityWindow embeddedWindow = createMockAccessibilityWindow(
- mA11yWindowTokens.get(embeddedWindowId), Display.DEFAULT_DISPLAY);
- setRegionForMockAccessibilityWindow(embeddedWindow, new Region(embeddingBounds));
- mWindows.get(Display.DEFAULT_DISPLAY).set(0, embeddedWindow);
-
- final IBinder hostWindowLeashToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, hostWindowLeashToken, USER_SYSTEM_ID);
- final AccessibilityWindow hostWindow = createMockAccessibilityWindow(
- mA11yWindowTokens.get(hostWindowId), Display.DEFAULT_DISPLAY);
- setRegionForMockAccessibilityWindow(hostWindow, new Region(embeddingBounds));
- mWindows.get(Display.DEFAULT_DISPLAY).set(1, hostWindow);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(
- hostWindowLeashToken, embeddedWindowLeashToken);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, not(hasItem(windowId(embeddedWindowId))));
- assertThat(a11yWindows.get(0), windowId(hostWindowId));
- final Rect bounds = new Rect();
- a11yWindows.get(0).getBoundsInScreen(bounds);
- assertEquals(bounds, embeddingBounds);
- }
-
- @Test
- public void onWindowsChanged_shouldNotReportfullyOccludedWindow() {
- final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(frontWindow, new Region(100, 100, 300, 300));
- final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
-
- // index 1 is focused. Let's use the next one for this test.
- final AccessibilityWindow occludedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(2);
- setRegionForMockAccessibilityWindow(occludedWindow, new Region(150, 150, 250, 250));
- final int occludedWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, occludedWindow.getWindowInfo().token);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasItem(windowId(frontWindowId)));
- assertThat(a11yWindows, not(hasItem(windowId(occludedWindowId))));
- }
-
- @Test
- public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- assertNotEquals("new title",
- toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
- .get(0).getTitle()));
-
- mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertEquals("new title",
- toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
- .get(0).getTitle()));
- }
-
- @Test
- public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
- throws RemoteException {
- final AccessibilityWindowInfo oldWindow =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- true, USER_SYSTEM_ID);
- mWindows.get(Display.DEFAULT_DISPLAY).set(0,
- createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertNotEquals(oldWindow,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
- }
-
- @Test
- public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
- final WindowInfo focusedWindowInfo =
- mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
- final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
- focusedWindowInfo.focused = false;
- windowInfo.focused = true;
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
- .isFocused());
- }
-
- @Test
- public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
- for (int i = 0; i < NUM_OF_WINDOWS; i++) {
- final int windowId = mA11yWindowTokens.keyAt(i);
- final IWindow windowToken = mA11yWindowTokens.valueAt(i);
- assertNotNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
-
- mA11yWindowManager.removeAccessibilityInteractionConnection(windowToken);
- assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void remoteAccessibilityConnection_binderDied_shouldRemoveConnection() {
- for (int i = 0; i < NUM_OF_WINDOWS; i++) {
- final int windowId = mA11yWindowTokens.keyAt(i);
- final RemoteAccessibilityConnection remoteA11yConnection =
- mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId);
- assertNotNull(remoteA11yConnection);
-
- remoteA11yConnection.binderDied();
- assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void getWindowTokenForUserAndWindowId_shouldNotNull() {
- final List<AccessibilityWindowInfo> windows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < windows.size(); i++) {
- final int windowId = windows.get(i).getId();
-
- assertNotNull(mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
- USER_SYSTEM_ID, windowId));
- }
- }
-
- @Test
- public void findWindowId() {
- final List<AccessibilityWindowInfo> windows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- for (int i = 0; i < windows.size(); i++) {
- final int windowId = windows.get(i).getId();
- final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
- USER_SYSTEM_ID, windowId);
-
- assertEquals(mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, windowToken), windowId);
- }
- }
-
- @Test
- public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId()
- throws RemoteException {
- final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false,
- Mockito.mock(IBinder.class), USER_SYSTEM_ID);
- assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
- }
-
- @Test
- public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() {
- final int windowId = -1;
- assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
- }
-
- @Test
- public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId()
- throws RemoteException {
- final IBinder mockHostToken = Mockito.mock(IBinder.class);
- final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockHostToken, USER_SYSTEM_ID);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockEmbeddedToken, USER_SYSTEM_ID);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
-
- final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
- embeddedWindowId);
- assertEquals(hostWindowId, resolvedWindowId);
- }
-
- @Test
- public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId()
- throws RemoteException {
- final IBinder mockHostToken = Mockito.mock(IBinder.class);
- final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
- final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockHostToken, USER_SYSTEM_ID);
- final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, mockEmbeddedToken, USER_SYSTEM_ID);
-
- mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
- mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken);
-
- final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
- embeddedWindowId);
- assertNotEquals(hostWindowId, resolvedWindowId);
- assertEquals(embeddedWindowId, resolvedWindowId);
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
- // Updates top 2 z-order WindowInfo are whole visible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(0).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
-
- windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
- // Updates z-order #1 WindowInfo is half visible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- // Note that the second window is also exposed even if region is empty because it's focused.
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertTrue(outBounds.getBounds().isEmpty());
- }
-
- @Test
- public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
- // Updates z-order #0 WindowInfo to have two interact-able areas.
- final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
- region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
- setRegionForMockAccessibilityWindow(firstWindow, region);
- final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
- setRegionForMockAccessibilityWindow(secondWindow,
- new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- final List<AccessibilityWindowInfo> a11yWindows =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
- assertThat(a11yWindows, hasSize(2));
- final Region outBounds = new Region();
- final int windowId = a11yWindows.get(1).getId();
-
- mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
- assertFalse(outBounds.getBounds().isEmpty());
- assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
- assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT - 400));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
- final IBinder eventWindowToken =
- mWindows.get(Display.DEFAULT_DISPLAY)
- .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
- final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, eventWindowToken);
- when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
- .thenReturn(eventWindowToken);
-
- final int noUse = 0;
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- is(eventWindowId));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_hoverEvent_touchInteract_shouldSetActiveWindow() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- assertThat(currentActiveWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(currentActiveWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_shouldUpdateA11yFocus() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX);
- final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
- assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
-
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary()
- throws RemoteException {
- runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault()
- throws RemoteException {
- runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY);
- }
-
- private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
- int initialDisplayId, int eventDisplayId) throws RemoteException {
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
- final int initialWindowId = getWindowIdFromWindowInfosForDisplay(
- initialDisplayId, DEFAULT_FOCUSED_INDEX);
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- initialWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId));
- Mockito.reset(mMockA11yEventSender);
-
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(
- eventDisplayId, DEFAULT_FOCUSED_INDEX);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(initialDisplayId),
- eventWindowId(initialWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(eventDisplayId),
- eventWindowId(eventWindowId),
- a11yWindowChanges(
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
- }
-
- @Test
- public void updateActiveAndA11yFocusedWindow_clearA11yFocusEvent_shouldClearA11yFocus() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX);
- final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
- assertThat(currentA11yFocusedWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
- noUse);
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
- is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
- }
-
- @Test
- public void onTouchInteractionEnd_shouldRollbackActiveWindow() {
- final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- assertThat(currentActiveWindowId, is(not(eventWindowId)));
-
- final int noUse = 0;
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- eventWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
- // AccessibilityEventSender is invoked after active window changed. Reset it.
- Mockito.reset(mMockA11yEventSender);
-
- mA11yWindowManager.onTouchInteractionEnd();
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(2))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(eventWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- assertThat(captor.getAllValues().get(1),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(currentActiveWindowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
- }
-
- @Test
- public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
- throws RemoteException {
- final IBinder defaultFocusWinToken =
- mWindows.get(Display.DEFAULT_DISPLAY).get(
- DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
- final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, defaultFocusWinToken);
- when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
- .thenReturn(defaultFocusWinToken);
- final int newFocusWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
- DEFAULT_FOCUSED_INDEX + 1);
- final IAccessibilityInteractionConnection mockNewFocusConnection =
- mA11yWindowManager.getConnectionLocked(
- USER_SYSTEM_ID, newFocusWindowId).getRemote();
-
- mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
- final int noUse = 0;
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- defaultFocusWindowId,
- noUse,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(defaultFocusWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
- is(defaultFocusWindowId));
-
- mA11yWindowManager.onTouchInteractionStart();
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- newFocusWindowId,
- noUse,
- AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- noUse);
- mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
- newFocusWindowId,
- AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
- noUse);
- assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(newFocusWindowId));
- assertThat(mA11yWindowManager.getFocusedWindowId(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(newFocusWindowId));
-
- mA11yWindowManager.onTouchInteractionEnd();
- mHandler.sendLastMessage();
- verify(mockNewFocusConnection).clearAccessibilityFocus();
- }
-
- @Test
- public void getPictureInPictureWindow_shouldNotNull() {
- assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- }
-
- @Test
- public void notifyOutsideTouch() throws RemoteException {
- final int targetWindowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 1);
- final int outsideWindowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- final IAccessibilityInteractionConnection mockRemoteConnection =
- mA11yWindowManager.getConnectionLocked(
- USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
- true;
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
-
- mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
- verify(mockRemoteConnection).notifyOutsideTouch();
- }
-
- @Test
- public void addAccessibilityInteractionConnection_profileUser_findInParentUser()
- throws RemoteException {
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, USER_PROFILE);
- final int windowId = mA11yWindowManager.findWindowIdLocked(
- USER_PROFILE_PARENT, token.asBinder());
- assertTrue(windowId >= 0);
- }
-
- @Test
- public void getDisplayList() throws RemoteException {
- // Starts tracking window of second display.
- startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
-
- final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
- DISPLAY_TYPE_DEFAULT);
- assertTrue(displayList.equals(mExpectedDisplayList));
- }
-
- @Test
- public void setAccessibilityWindowIdToSurfaceMetadata()
- throws RemoteException {
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- true, USER_SYSTEM_ID);
- int windowId = -1;
- for (int i = 0; i < mA11yWindowTokens.size(); i++) {
- if (mA11yWindowTokens.valueAt(i).equals(token)) {
- windowId = mA11yWindowTokens.keyAt(i);
- }
- }
- assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
- verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
- token.asBinder(), windowId);
-
- mA11yWindowManager.removeAccessibilityInteractionConnection(token);
- verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
- token.asBinder(), -1);
- }
-
- @Test
- public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertEquals(hostToken, mMockHostToken);
- }
-
- @Test
- public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() {
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- mA11yWindowManager.disassociateLocked(mMockEmbeddedToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() {
- mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
- mA11yWindowManager.disassociateLocked(mMockHostToken);
- final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken);
- assertNull(hostToken);
- }
-
- @Test
- public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() {
- final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken);
- assertEquals(windowId, HOST_WINDOW_ID);
- }
-
- @Test
- public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() {
- final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken);
- assertEquals(windowId, INVALID_ID);
- }
-
- @Test
- public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
- final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
- assertEquals(token, mMockHostToken);
- }
-
- @Test
- public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
- final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
- assertNull(token);
- }
-
- @Test
- public void setAccessibilityWindowAttributes_windowIsNotRegistered_titleIsChanged() {
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
- layoutParams.accessibilityTitle = "accessibility window title";
- final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
- layoutParams, new LocaleList());
-
- mA11yWindowManager.setAccessibilityWindowAttributes(Display.DEFAULT_DISPLAY, windowId,
- USER_SYSTEM_ID, attributes);
-
- final AccessibilityWindowInfo a11yWindow = mA11yWindowManager.findA11yWindowInfoByIdLocked(
- windowId);
- assertEquals(toString(layoutParams.accessibilityTitle), toString(a11yWindow.getTitle()));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
-
- // Removing index 0 because it's not focused, and avoids unnecessary layer change.
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- windows.remove(0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
-
- final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
- false, USER_SYSTEM_ID);
- // Adding window to the front so that other windows' layer won't change.
- windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
- }
-
- @Test
- public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
- windows.get(0).getWindowInfo().title = "new title";
- final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
-
- onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
-
- final ArgumentCaptor<AccessibilityEvent> captor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
- verify(mMockA11yEventSender, times(1))
- .sendAccessibilityEventForCurrentUserLocked(captor.capture());
- assertThat(captor.getAllValues().get(0),
- allOf(displayId(Display.DEFAULT_DISPLAY),
- eventWindowId(windowId),
- a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
- }
-
- private void registerLeashedTokenAndWindowId() {
- mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
- mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
- }
-
- private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
- // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
- // mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
- // for the test.
- for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
- final IWindow token = addAccessibilityInteractionConnection(displayId,
- true, USER_SYSTEM_ID);
- windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
-
- }
- for (int i = 0; i < NUM_APP_WINDOWS; i++) {
- final IWindow token = addAccessibilityInteractionConnection(displayId,
- false, USER_SYSTEM_ID);
- windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
- }
- // Sets up current focused window of display.
- // Each display has its own current focused window if config_perDisplayFocusEnabled is true.
- // Otherwise only default display needs to current focused window.
- if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
- }
- // Turns on windows tracking, and update window info.
- mA11yWindowManager.startTrackingWindows(displayId, false);
- // Puts window lists into array.
- mWindows.put(displayId, windowsForDisplay);
- // Sets the default display is the top focused display and
- // its current focused window is the top focused window.
- if (displayId == Display.DEFAULT_DISPLAY) {
- setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
- }
- // Invokes callback for sending window lists to A11y framework.
- onAccessibilityWindowsChanged(displayId, FORCE_SEND);
-
- assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowsForDisplay.size());
- }
-
- private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
- ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
- ArgumentCaptor.forClass(
- WindowsForAccessibilityCallback.class);
- verify(mMockWindowManagerInternal)
- .setWindowsForAccessibilityCallback(eq(displayId),
- windowsForAccessibilityCallbacksCaptor.capture());
- return windowsForAccessibilityCallbacksCaptor.getValue();
- }
-
- private IWindow addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
- int userId) throws RemoteException {
- final IWindow mockWindowToken = Mockito.mock(IWindow.class);
- final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
- IAccessibilityInteractionConnection.class);
- final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
- final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
- final IBinder mockLeashToken = Mockito.mock(IBinder.class);
- when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
- when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
- when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
- .thenReturn(bGlobal);
- when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
- .thenReturn(displayId);
-
- int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
- mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId);
- mA11yWindowTokens.put(windowId, mockWindowToken);
- return mockWindowToken;
- }
-
- private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
- IBinder leashToken, int userId) throws RemoteException {
- final IWindow mockWindowToken = Mockito.mock(IWindow.class);
- final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
- IAccessibilityInteractionConnection.class);
- final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
- final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
- when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
- when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
- when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
- .thenReturn(bGlobal);
- when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
- .thenReturn(displayId);
-
- int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
- mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId);
- mA11yWindowTokens.put(windowId, mockWindowToken);
- return windowId;
- }
-
- private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
- return mA11yWindowManager.findWindowIdLocked(
- USER_SYSTEM_ID, windowToken);
- }
-
- private void setTopFocusedWindowAndDisplay(int displayId, int index) {
- // Sets the top focus window.
- mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
- // Sets the top focused display.
- mTopFocusedDisplayId = displayId;
- }
-
- private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
- WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
- if (callbacks == null) {
- callbacks = getWindowsForAccessibilityCallbacks(displayId);
- mCallbackOfWindows.put(displayId, callbacks);
- }
- callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
- mWindows.get(displayId));
- }
-
- private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
- int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId,
- int oldFocusedWindowIndex) {
- if (mSupportPerDisplayFocus) {
- // Gets the old focused window of display which wants to change focused window.
- WindowInfo focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
- // Resets the focus of old focused window.
- focusedWindowInfo.focused = false;
- // Gets the new window of display which wants to change focused window.
- focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
- // Sets the focus of new focused window.
- focusedWindowInfo.focused = true;
- } else {
- // Gets the window of display which wants to change focused window.
- WindowInfo focusedWindowInfo =
- mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
- // Sets the focus of new focused window.
- focusedWindowInfo.focused = true;
- // Gets the old focused window of old top focused display.
- focusedWindowInfo =
- mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
- // Resets the focus of old focused window.
- focusedWindowInfo.focused = false;
- // Changes the top focused display and window.
- setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex);
- }
- }
-
- private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
-
- final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
- when(window.getWindowInfo()).thenReturn(windowInfo);
- when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
- when(window.isTouchable()).thenReturn(true);
- when(window.getType()).thenReturn(windowInfo.type);
-
- setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
- return window;
- }
-
- private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
- doAnswer(invocation -> {
- ((Region) invocation.getArgument(0)).set(region);
- return null;
- }).when(window).getTouchableRegionInScreen(any(Region.class));
- doAnswer(invocation -> {
- ((Region) invocation.getArgument(0)).set(region);
- return null;
- }).when(window).getTouchableRegionInWindow(any(Region.class));
- }
-
- private Region nextToucableRegion(int displayId) {
- final int topLeft = mNextRegionOffsets.get(displayId, 0);
- final int bottomRight = topLeft + 100;
- mNextRegionOffsets.put(displayId, topLeft + 10);
- return new Region(topLeft, topLeft, bottomRight, bottomRight);
- }
-
- @Nullable
- private static String toString(@Nullable CharSequence cs) {
- return cs == null ? null : cs.toString();
- }
-
- static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private final int mDisplayId;
-
- DisplayIdMatcher(int displayId) {
- super();
- mDisplayId = displayId;
- }
-
- static DisplayIdMatcher displayId(int displayId) {
- return new DisplayIdMatcher(displayId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getDisplayId() == mDisplayId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to displayId " + mDisplayId);
- }
- }
-
- static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private int mWindowId;
-
- EventWindowIdMatcher(int windowId) {
- super();
- mWindowId = windowId;
- }
-
- static EventWindowIdMatcher eventWindowId(int windowId) {
- return new EventWindowIdMatcher(windowId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getWindowId() == mWindowId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to windowId " + mWindowId);
- }
- }
-
- static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
- private int mWindowChanges;
-
- WindowChangesMatcher(int windowChanges) {
- super();
- mWindowChanges = windowChanges;
- }
-
- static WindowChangesMatcher a11yWindowChanges(int windowChanges) {
- return new WindowChangesMatcher(windowChanges);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityEvent event) {
- return event.getWindowChanges() == mWindowChanges;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to window changes " + mWindowChanges);
- }
- }
-
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
- private final int mWindowId;
-
- WindowIdMatcher(int windowId) {
- super();
- mWindowId = windowId;
- }
-
- static WindowIdMatcher windowId(int windowId) {
- return new WindowIdMatcher(windowId);
- }
-
- @Override
- protected boolean matchesSafely(AccessibilityWindowInfo window) {
- return window.getId() == mWindowId;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Matching to windowId " + mWindowId);
- }
- }
-}
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index fe974e3..af87bf7 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -1014,7 +1014,7 @@
triggerFailureCount = 1;
}
for (int i = 0; i < triggerFailureCount; i++) {
- watchdog.onPackageFailure(packages, failureReason);
+ watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
if (Flags.recoverabilityDetection()) {
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c25bed2..5a8a6be 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -392,7 +392,7 @@
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
@@ -1025,14 +1025,14 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Fail APP_A below the threshold which should not trigger package failures
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mHealthCheckFailedPackages).isEmpty();
// One more to trigger the package failure
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
@@ -1051,10 +1051,10 @@
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1062,10 +1062,10 @@
// DEFAULT_TRIGGER_FAILURE_DURATION_MS.
assertThat(observer.mHealthCheckFailedPackages).isEmpty();
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1129,17 +1129,17 @@
watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
// Raise 2 failures at t=0 and t=900 respectively
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(900);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
// Raise 2 failures at t=1100
moveTimeForwardAndDispatch(200);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1433,13 +1433,13 @@
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
- watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEmpty();
watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
- watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEqualTo(List.of(APP_A));
@@ -1737,7 +1737,7 @@
triggerFailureCount = 1;
}
for (int i = 0; i < triggerFailureCount; i++) {
- watchdog.onPackageFailure(packages, failureReason);
+ watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
if (Flags.recoverabilityDetection()) {