Merge changes from topic "b249277686_shadeCs" into udc-qpr-dev
* changes:
Move shade code out of CentralSurfaces
Move shade code out of CentralSurfaces
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 81258f1..6e0fc4f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1084,6 +1084,16 @@
}
/**
+ * Update the forced status bar appearance.
+ * @hide
+ */
+ @Override
+ public void updateStatusBarAppearance(int appearance) {
+ mTaskDescription.setStatusBarAppearance(appearance);
+ setTaskDescription(mTaskDescription);
+ }
+
+ /**
* Update the forced navigation bar color.
* @hide
*/
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5ee895..ff0f437 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
import android.window.TaskSnapshot;
import com.android.internal.app.LocalePicker;
@@ -1553,6 +1554,8 @@
private int mColorBackgroundFloating;
private int mStatusBarColor;
private int mNavigationBarColor;
+ @Appearance
+ private int mStatusBarAppearance;
private boolean mEnsureStatusBarContrastWhenTransparent;
private boolean mEnsureNavigationBarContrastWhenTransparent;
private int mResizeMode;
@@ -1653,8 +1656,8 @@
final Icon icon = mIconRes == Resources.ID_NULL ? null :
Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
- mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
- -1, -1, 0);
+ mStatusBarColor, mNavigationBarColor, 0, false, false,
+ RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
}
@@ -1672,7 +1675,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1690,7 +1693,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1702,7 +1705,7 @@
*/
@Deprecated
public TaskDescription(String label) {
- this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1712,7 +1715,7 @@
*/
@Deprecated
public TaskDescription() {
- this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1728,7 +1731,7 @@
@Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
- false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1744,14 +1747,15 @@
*/
@Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
- RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+ false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/** @hide */
public TaskDescription(@Nullable String label, @Nullable Icon icon,
int colorPrimary, int colorBackground,
int statusBarColor, int navigationBarColor,
+ @Appearance int statusBarAppearance,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
int minHeight, int colorBackgroundFloating) {
@@ -1761,6 +1765,7 @@
mColorBackground = colorBackground;
mStatusBarColor = statusBarColor;
mNavigationBarColor = navigationBarColor;
+ mStatusBarAppearance = statusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
ensureNavigationBarContrastWhenTransparent;
@@ -1789,6 +1794,7 @@
mColorBackground = other.mColorBackground;
mStatusBarColor = other.mStatusBarColor;
mNavigationBarColor = other.mNavigationBarColor;
+ mStatusBarAppearance = other.mStatusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1818,6 +1824,9 @@
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
+ if (other.mStatusBarAppearance != 0) {
+ mStatusBarAppearance = other.mStatusBarAppearance;
+ }
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
@@ -2089,6 +2098,14 @@
/**
* @hide
*/
+ @Appearance
+ public int getStatusBarAppearance() {
+ return mStatusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public void setEnsureStatusBarContrastWhenTransparent(
boolean ensureStatusBarContrastWhenTransparent) {
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2097,6 +2114,13 @@
/**
* @hide
*/
+ public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+ mStatusBarAppearance = statusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public boolean getEnsureNavigationBarContrastWhenTransparent() {
return mEnsureNavigationBarContrastWhenTransparent;
}
@@ -2218,6 +2242,7 @@
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
dest.writeInt(mNavigationBarColor);
+ dest.writeInt(mStatusBarAppearance);
dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
dest.writeInt(mResizeMode);
@@ -2241,6 +2266,7 @@
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
mNavigationBarColor = source.readInt();
+ mStatusBarAppearance = source.readInt();
mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
mResizeMode = source.readInt();
@@ -2289,6 +2315,7 @@
&& mColorBackground == other.mColorBackground
&& mStatusBarColor == other.mStatusBarColor
&& mNavigationBarColor == other.mNavigationBarColor
+ && mStatusBarAppearance == other.mStatusBarAppearance
&& mEnsureStatusBarContrastWhenTransparent
== other.mEnsureStatusBarContrastWhenTransparent
&& mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7596459..2f04b0c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@
void updateStatusBarColor(int color);
/**
+ * Update the status bar appearance.
+ */
+
+ void updateStatusBarAppearance(int appearance);
+
+ /**
* Update the navigation bar color to a forced one.
*/
void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@
if (mDecorCallback != null) {
mDecorCallback.onSystemBarAppearanceChanged(appearance);
}
+ if (mWindowControllerCallback != null) {
+ mWindowControllerCallback.updateStatusBarAppearance(appearance);
+ }
}
/** @hide */
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index c37e311..c9afdc0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
@@ -446,6 +447,9 @@
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
+ // Created here in order to live across activity and session changes
+ @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
/** @hide */
public interface ContentCaptureClient {
/**
@@ -456,12 +460,15 @@
}
/** @hide */
- static class StrippedContext {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static class StrippedContext {
@NonNull final String mPackageName;
@NonNull final String mContext;
final @UserIdInt int mUserId;
- private StrippedContext(@NonNull Context context) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public StrippedContext(@NonNull Context context) {
mPackageName = context.getPackageName();
mContext = context.toString();
mUserId = context.getUserId();
@@ -502,6 +509,16 @@
mHandler = Handler.createAsync(Looper.getMainLooper());
mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+ if (mOptions.contentProtectionOptions.enableReceiver
+ && mOptions.contentProtectionOptions.bufferSize > 0) {
+ mContentProtectionEventBuffer =
+ new RingBuffer(
+ ContentCaptureEvent.class,
+ mOptions.contentProtectionOptions.bufferSize);
+ } else {
+ mContentProtectionEventBuffer = null;
+ }
}
/**
@@ -870,6 +887,13 @@
activity.addDumpable(mDumpable);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+ return mContentProtectionEventBuffer;
+ }
+
// NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
private final class Dumper implements Dumpable {
@Override
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000..0840b66
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+ private static final String TAG = "ContentProtectionEventProcessor";
+
+ private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+ private static final List<String> PASSWORD_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+ private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+ private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+ private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+ @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+ @NonNull private final Handler mHandler;
+
+ @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+ @NonNull private final String mPackageName;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mPasswordFieldDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mSuspiciousTextDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public Instant mLastFlushTime;
+
+ public ContentProtectionEventProcessor(
+ @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+ @NonNull Handler handler,
+ @NonNull IContentCaptureManager contentCaptureManager,
+ @NonNull String packageName) {
+ mEventBuffer = eventBuffer;
+ mHandler = handler;
+ mContentCaptureManager = contentCaptureManager;
+ mPackageName = packageName;
+ }
+
+ /** Main entry point for {@link ContentCaptureEvent} processing. */
+ @UiThread
+ public void processEvent(@NonNull ContentCaptureEvent event) {
+ if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+ storeEvent(event);
+ }
+ if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+ processViewAppearedEvent(event);
+ }
+ }
+
+ @UiThread
+ private void storeEvent(@NonNull ContentCaptureEvent event) {
+ // Ensure receiver gets the package name which might not be set
+ ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+ viewNode.setTextIdEntry(mPackageName);
+ event.setViewNode(viewNode);
+ mEventBuffer.append(event);
+ }
+
+ @UiThread
+ private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+ mPasswordFieldDetected |= isPasswordField(event);
+ mSuspiciousTextDetected |= isSuspiciousText(event);
+ if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ loginDetected();
+ }
+ }
+
+ @UiThread
+ private void loginDetected() {
+ if (mLastFlushTime == null
+ || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+ flush();
+ }
+ mPasswordFieldDetected = false;
+ mSuspiciousTextDetected = false;
+ }
+
+ @UiThread
+ private void flush() {
+ mLastFlushTime = Instant.now();
+
+ // Note the thread annotations, do not move clearEvents to mHandler
+ ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+ mHandler.post(() -> handlerOnLoginDetected(events));
+ }
+
+ @UiThread
+ @NonNull
+ private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+ List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+ mEventBuffer.clear();
+ return new ParceledListSlice<>(events);
+ }
+
+ private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+ try {
+ // TODO(b/275732576): Call mContentCaptureManager
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+ }
+ }
+
+ private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+ return isPasswordField(event.getViewNode());
+ }
+
+ private boolean isPasswordField(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return false;
+ }
+ return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+ }
+
+ private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+ if (!isAndroidViewNode(viewNode)) {
+ return false;
+ }
+ int inputType = viewNode.getInputType();
+ return PASSWORD_FIELD_INPUT_TYPES.stream()
+ .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+ }
+
+ private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+ if (viewNode.getClassName() != null) {
+ return false;
+ }
+ return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+ }
+
+ private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+ String className = viewNode.getClassName();
+ return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+ }
+
+ private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+ return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+ || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+ }
+
+ private boolean isSuspiciousText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ if (isPasswordText(text)) {
+ return true;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+ .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+ }
+
+ private boolean isPasswordText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return PASSWORD_TEXTS.stream()
+ .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213..5ac99db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -152,6 +153,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -167,6 +169,7 @@
0x2222222, // colorBackground
0x3333332, // statusBarColor
0x4444442, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -197,6 +200,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -219,6 +223,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -250,6 +255,7 @@
assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
assertEquals(td1.getResizeMode(), td2.getResizeMode());
assertEquals(td1.getMinWidth(), td2.getMinWidth());
assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c4..101f7c2 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
import android.content.ContentCaptureOptions;
import android.content.Context;
+import com.android.internal.util.RingBuffer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -37,9 +40,15 @@
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureManagerTest {
+ private static final int BUFFER_SIZE = 100;
+
+ private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
@Mock
private Context mMockContext;
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
@Test
public void testConstructor_invalidParametersThrowsException() {
assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@
}
@Test
+ public void testConstructor_contentProtection_default_bufferNotCreated() {
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ false, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_enabled_bufferCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+ RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+ assertThat(buffer).isNotNull();
+ ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+ int offset = 3;
+ for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+ ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+ buffer.append(event);
+ expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+ }
+ assertThat(buffer.toArray()).isEqualTo(expected);
+ }
+
+ @Test
public void testRemoveData_invalidParametersThrowsException() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThrows(NullPointerException.class, () -> manager.removeData(null));
}
@@ -60,10 +123,8 @@
@Test
@SuppressWarnings("GuardedBy")
public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@
manager.setFlushViewTreeAppearingEventDisabled(false);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
}
+
+ private ContentCaptureOptions createOptions(
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ /* maxBufferSize= */ 0,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ /* enableReceiver= */ true,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 0000000..fd5627d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+ private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+ private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+ private static final String SAFE_TEXT = "SAFE TEXT";
+
+ private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+ @Before
+ public void setup() {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mMockEventBuffer,
+ new Handler(Looper.getMainLooper()),
+ mMockContentCaptureManager,
+ PACKAGE_NAME);
+ }
+
+ @Test
+ public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+ List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+ for (int type = -100; type <= 100; type++) {
+ ContentCaptureEvent event = createEvent(type);
+ if (EVENT_TYPES_TO_STORE.contains(type)) {
+ expectedEvents.add(event);
+ }
+
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+
+ assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+ expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+ verifyNoMoreInteractions(mMockEventBuffer);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+ ContentCaptureEvent event = createStoreEvent();
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isNotNull();
+ assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+ ViewNode viewNode = new ViewNode();
+ viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+ ContentCaptureEvent event = createStoreEvent();
+ event.setViewNode(viewNode);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+ assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ for (int type = -100; type <= 100; type++) {
+ if (type == TYPE_VIEW_APPEARED) {
+ continue;
+ }
+
+ mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ }
+
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_passwordFieldNotDetected() {
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_suspiciousTextNotDetected() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_withoutViewNode() {
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_belowFlushThreshold() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_aboveFlushThreshold() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, times(2)).clear();
+ verify(mMockEventBuffer, times(2)).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_withoutClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_wrongClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ "wrong.prefix" + ANDROID_CLASS_NAME,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_wrongInputType() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withClassName() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withSafeViewNodeText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withEventText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_withSafeText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_passwordText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_passwordText() {
+ // Specify the class to differ from {@link isPasswordField_webView} test in this version
+ ContentCaptureEvent event =
+ createProcessEvent(
+ "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ private static ContentCaptureEvent createEvent(int type) {
+ return new ContentCaptureEvent(/* sessionId= */ 123, type);
+ }
+
+ private static ContentCaptureEvent createStoreEvent() {
+ return createEvent(TYPE_VIEW_TEXT_CHANGED);
+ }
+
+ private static ContentCaptureEvent createProcessEvent() {
+ return createEvent(TYPE_VIEW_APPEARED);
+ }
+
+ private ContentCaptureEvent createProcessEvent(
+ @Nullable String className,
+ int inputType,
+ @Nullable String eventText,
+ @Nullable String viewNodeText) {
+ View view = new View(mContext);
+ ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+ if (className != null) {
+ viewStructure.setClassName(className);
+ }
+ if (viewNodeText != null) {
+ viewStructure.setText(viewNodeText);
+ }
+ viewStructure.setInputType(inputType);
+
+ ContentCaptureEvent event = createProcessEvent();
+ event.setViewNode(viewStructure.getNode());
+ if (eventText != null) {
+ event.setText(eventText);
+ }
+
+ return event;
+ }
+
+ private ContentCaptureEvent createAndroidPasswordFieldEvent(
+ @Nullable String className, int inputType) {
+ return createProcessEvent(
+ className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+ }
+
+ private ContentCaptureEvent createWebViewPasswordFieldEvent(
+ @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+ }
+
+ private ContentCaptureEvent createSuspiciousTextEvent(
+ @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(
+ /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 514ea52..76c80f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -4,7 +4,7 @@
import android.content.Context
import android.graphics.Color
import android.view.View
-
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
/**
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
@@ -23,6 +23,10 @@
* with the caption background color.
*/
protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0) {
+ Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d8a0bbb..9f884b2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -827,6 +827,11 @@
<!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
<string name="show_touches_summary">Show visual feedback for taps</string>
+ <!-- UI debug setting: show key presses? [CHAR LIMIT=25] -->
+ <string name="show_key_presses">Show key presses</string>
+ <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=50] -->
+ <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
<!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fe89883..6eb2f38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -484,8 +484,9 @@
if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
synchronized (mEntriesMap) {
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(packageName);
}
if (entry == null) {
ApplicationInfo info = getAppInfoLocked(packageName, userId);
@@ -735,8 +736,9 @@
private AppEntry getEntryLocked(ApplicationInfo info) {
int userId = UserHandle.getUserId(info.uid);
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(info.packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(info.packageName);
}
if (DEBUG) {
Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
@@ -752,8 +754,11 @@
Log.i(TAG, "Creating AppEntry for " + info.packageName);
}
entry = new AppEntry(mContext, info, mCurId++);
- mEntriesMap.get(userId).put(info.packageName, entry);
- mAppEntries.add(entry);
+ userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ userEntriesMap.put(info.packageName, entry);
+ mAppEntries.add(entry);
+ }
} else if (entry.info != info) {
entry.info = info;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 1d081d7..34d8148 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -804,7 +804,7 @@
}
@Test
- public void getEntry_validUserId_shouldReturnEntry() {
+ public void getEntry_hasCache_shouldReturnCacheEntry() {
mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
addApp(PKG_1, /* id= */ 1);
@@ -813,10 +813,13 @@
}
@Test
- public void getEntry_invalidUserId_shouldReturnNull() {
- mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
- addApp(PKG_1, /* id= */ 1);
+ public void getEntry_hasNoCache_shouldReturnEntry() {
+ mApplicationsState.mEntriesMap.clear();
+ ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
+ mApplicationsState.mApplications.add(appInfo);
+ mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
- assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ -1)).isNull();
+ assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
+ .isEqualTo(PKG_1);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928..b2ffea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -201,7 +201,9 @@
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
- mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+ if (!isOwnerInForeground()) {
+ mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+ }
}
};
@@ -239,33 +241,37 @@
}
}
+ private boolean isOwnerInForeground() {
+ final String clientPackage = mCurrentDialog.getOpPackageName();
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(clientPackage)
+ && !Utils.isSystem(mContext, clientPackage)) {
+ Log.w(TAG, "Evicting client due to: " + topPackage);
+ return false;
+ }
+ }
+ return true;
+ }
+
private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
if (mCurrentDialog != null) {
try {
- final String clientPackage = mCurrentDialog.getOpPackageName();
- Log.w(TAG, "Task stack changed, current client: " + clientPackage);
- final List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(clientPackage)
- && !Utils.isSystem(mContext, clientPackage)) {
- Log.e(TAG, "Evicting client due to: " + topPackage);
- mCurrentDialog.dismissWithoutCallback(true /* animate */);
- mCurrentDialog = null;
+ mCurrentDialog.dismissWithoutCallback(true /* animate */);
+ mCurrentDialog = null;
- for (Callback cb : mCallbacks) {
- cb.onBiometricPromptDismissed();
- }
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
- if (mReceiver != null) {
- mReceiver.onDialogDismissed(
- BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
- mReceiver = null;
- }
- }
+ if (mReceiver != null) {
+ mReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+ null /* credentialAttestation */);
+ mReceiver = null;
}
} catch (RemoteException e) {
Log.e(TAG, "Remote exception", e);
@@ -1253,10 +1259,11 @@
cb.onBiometricPromptShown();
}
mCurrentDialog = newDialog;
- mCurrentDialog.show(mWindowManager, savedState);
- if (!promptInfo.isAllowBackgroundAuthentication()) {
- mHandler.post(this::cancelIfOwnerIsNotInForeground);
+ if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
+ cancelIfOwnerIsNotInForeground();
+ } else {
+ mCurrentDialog.show(mWindowManager, savedState);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db1..c2421dc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
import android.service.dreams.DreamService;
-import android.util.Log;
import androidx.annotation.NonNull;
@@ -52,7 +51,6 @@
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
private static final String TAG = "DreamOverlayStateCtlr";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +108,17 @@
private final int mSupportedTypes;
+ private final DreamLogger mLogger;
+
@VisibleForTesting
@Inject
public DreamOverlayStateController(@Main Executor executor,
@Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ DreamLogger dreamLogger) {
mExecutor = executor;
mOverlayEnabled = overlayEnabled;
+ mLogger = dreamLogger;
mFeatureFlags = featureFlags;
if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +126,7 @@
} else {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
}
- if (DEBUG) {
- Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
- }
+ mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
}
/**
@@ -134,18 +134,14 @@
*/
public void addComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring adding complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring adding complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
- if (DEBUG) {
- Log.d(TAG, "addComplication: added " + complication);
- }
+ mLogger.d(TAG, "Added dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -156,18 +152,14 @@
*/
public void removeComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring removing complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring removing complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
- if (DEBUG) {
- Log.d(TAG, "removeComplication: removed " + complication);
- }
+ mLogger.d(TAG, "Removed dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -313,6 +305,7 @@
* @param active {@code true} if overlay is active, {@code false} otherwise.
*/
public void setOverlayActive(boolean active) {
+ mLogger.d(TAG, "Dream overlay active: " + active);
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
}
@@ -321,6 +314,8 @@
* @param active {@code true} if low light mode is active, {@code false} otherwise.
*/
public void setLowLightActive(boolean active) {
+ mLogger.d(TAG, "Low light mode active: " + active);
+
if (isLowLightActive() && !active) {
// Notify that we're exiting low light only on the transition from active to not active.
mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@
* @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
*/
public void setHasAssistantAttention(boolean hasAttention) {
+ mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
}
@@ -359,6 +355,7 @@
* @param visible {@code true} if the status bar is visible, {@code false} otherwise.
*/
public void setDreamOverlayStatusBarVisible(boolean visible) {
+ mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
modifyState(
visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
}
@@ -376,6 +373,7 @@
*/
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Available complication types: " + types);
mAvailableComplicationTypes = types;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
@@ -393,6 +391,7 @@
*/
public void setShouldShowComplications(boolean shouldShowComplications) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
mShouldShowComplications = shouldShowComplications;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfed..1865c38 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
}
+ protected static String getLoggableStatusIconType(@StatusIconType int type) {
+ return switch (type) {
+ case STATUS_ICON_NOTIFICATIONS -> "notifications";
+ case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+ case STATUS_ICON_ALARM_SET -> "alarm_set";
+ case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+ case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+ case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+ case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+ case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+ default -> type + "(unknown)";
+ };
+ }
+
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
View icon = mStatusIcons.get(iconType);
if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98..3a28408 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -61,6 +61,8 @@
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+ private static final String TAG = "DreamStatusBarCtrl";
+
private final ConnectivityManager mConnectivityManager;
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
private final NextAlarmController mNextAlarmController;
@@ -78,6 +80,7 @@
private final Executor mMainExecutor;
private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
new ArrayList<>();
+ private final DreamLogger mLogger;
private boolean mIsAttached;
@@ -157,7 +160,8 @@
StatusBarWindowStateController statusBarWindowStateController,
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
DreamOverlayStateController dreamOverlayStateController,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ DreamLogger dreamLogger) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -173,6 +177,7 @@
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
mUserTracker = userTracker;
+ mLogger = dreamLogger;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +346,8 @@
@Nullable String contentDescription) {
mMainExecutor.execute(() -> {
if (mIsAttached) {
+ mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
+ + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
mView.showIcon(iconType, show, contentDescription);
}
});
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 cdc7cee..c452c0f 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
@@ -104,13 +104,13 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -315,7 +315,7 @@
}
};
private NotificationStackScrollLogger mLogger;
- private CentralSurfaces mCentralSurfaces;
+ private NotificationsController mNotificationsController;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
@@ -3989,7 +3989,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mCentralSurfaces.resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
cancelActiveSwipe();
@@ -4570,8 +4570,8 @@
return max + getStackTranslation();
}
- public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- this.mCentralSurfaces = centralSurfaces;
+ public void setNotificationsController(NotificationsController notificationsController) {
+ this.mNotificationsController = notificationsController;
}
public void setActivityStarter(ActivityStarter activityStarter) {
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 a52c843..12b2ef8 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
@@ -100,6 +100,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -146,6 +147,7 @@
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
+ private final NotificationsController mNotificationsController;
private final NotificationVisibilityProvider mVisibilityProvider;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -431,7 +433,7 @@
@Override
public void onSnooze(StatusBarNotification sbn,
NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
- mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+ mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
}
@Override
@@ -616,6 +618,7 @@
NotificationStackScrollLayout view,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
NotificationGutsManager notificationGutsManager,
+ NotificationsController notificationsController,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
@@ -664,6 +667,7 @@
mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
+ mNotificationsController = notificationsController;
mVisibilityProvider = visibilityProvider;
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -714,7 +718,7 @@
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setCentralSurfaces(mCentralSurfaces);
+ mView.setNotificationsController(mNotificationsController);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 427885b..0929a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -26,7 +26,6 @@
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
@@ -44,7 +43,6 @@
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -268,8 +266,6 @@
void readyForKeyguardDone();
- void resetUserExpandedStates();
-
void setLockscreenUser(int newUserId);
void showKeyguard();
@@ -358,9 +354,6 @@
boolean isDeviceInteractive();
- void setNotificationSnoozed(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
void awakenDreams();
void clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 69dbd2a..ddafa49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -172,7 +172,6 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
@@ -2220,7 +2219,7 @@
mNotificationShadeWindowController.setNotTouchable(false);
}
finishBarAnimations();
- resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
}
Trace.endSection();
}
@@ -2239,11 +2238,6 @@
}
};
- @Override
- public void resetUserExpandedStates() {
- mNotificationsController.resetUserExpandedStates();
- }
-
/**
* Notify the shade controller that the current user changed
*
@@ -3470,12 +3464,6 @@
};
@Override
- public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
- mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
- }
-
-
- @Override
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
try {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b9f92a0..b4a4a11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -954,6 +954,25 @@
eq(null) /* credentialAttestation */);
}
+ @Test
+ public void testShowDialog_whenOwnerNotInForeground() {
+ PromptInfo promptInfo = createTestPromptInfo();
+ promptInfo.setAllowBackgroundAuthentication(false);
+ switchTask("other_package");
+ mAuthController.showAuthenticationDialog(promptInfo,
+ mReceiver /* receiver */,
+ new int[]{1} /* sensorIds */,
+ false /* credentialAllowed */,
+ true /* requireConfirmation */,
+ 0 /* userId */,
+ 0 /* operationId */,
+ "testPackage",
+ REQUEST_ID);
+
+ assertNull(mAuthController.mCurrentDialog);
+ verify(mDialog1, never()).show(any(), any());
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c..461ec65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamLogger;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
@@ -56,6 +57,8 @@
private FakeFeatureFlags mFeatureFlags;
@Mock
private Observer mObserver;
+ @Mock
+ private DreamLogger mLogger;
@Before
public void setUp() {
@@ -66,7 +69,8 @@
mStateController = new DreamOverlayStateController(
mExecutor,
/* overlayEnabled= */ true,
- mFeatureFlags);
+ mFeatureFlags,
+ mLogger);
mLiveData = new ComplicationCollectionLiveData(mStateController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7b41605..2c1ebe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -58,6 +58,9 @@
@Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private DreamLogger mLogger;
+
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -405,6 +408,6 @@
}
private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
- return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+ return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index d16b757..5dc0e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -113,6 +113,8 @@
DreamOverlayStateController mDreamOverlayStateController;
@Mock
UserTracker mUserTracker;
+ @Mock
+ DreamLogger mLogger;
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -146,7 +148,8 @@
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
}
@Test
@@ -289,7 +292,8 @@
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 9938fa8..10aac4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
@@ -106,6 +107,7 @@
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationGutsManager mNotificationGutsManager;
+ @Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -455,6 +457,7 @@
mNotificationStackScrollLayout,
true,
mNotificationGutsManager,
+ mNotificationsController,
mVisibilityProvider,
mHeadsUpManager,
mNotificationRoundnessManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a4ee349..c0d98b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -79,9 +79,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -114,7 +114,7 @@
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -181,7 +181,7 @@
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
- mStackScroller.setCentralSurfaces(mCentralSurfaces);
+ mStackScroller.setNotificationsController(mNotificationsController);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 51359ad..9ff0746 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -88,11 +88,14 @@
import android.view.contentcapture.IDataShareWriteAdapter;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.GlobalWhitelistState;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -117,7 +120,7 @@
* with other sources to provide contextual data in other areas of the system
* such as Autofill.
*/
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -205,6 +208,8 @@
final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
new GlobalContentCaptureOptions();
+ @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
@@ -242,6 +247,14 @@
mServiceNameResolver.getServiceName(userId),
mServiceNameResolver.isTemporary(userId));
}
+
+ if (getEnableContentProtectionReceiverLocked()) {
+ mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context);
+ mContentProtectionBlocklistManager.updateBlocklist(
+ mDevCfgContentProtectionAppsBlocklistSize);
+ } else {
+ mContentProtectionBlocklistManager = null;
+ }
}
@Override // from AbstractMasterSystemService
@@ -397,7 +410,9 @@
}
}
- private void setFineTuneParamsFromDeviceConfig() {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ protected void setFineTuneParamsFromDeviceConfig() {
synchronized (mLock) {
mDevCfgMaxBufferSize =
DeviceConfig.getInt(
@@ -443,6 +458,8 @@
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+ // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+ // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -754,6 +771,25 @@
mGlobalContentCaptureOptions.dump(prefix2, pw);
}
+ /**
+ * Used by the constructor in order to be able to override the value in the tests.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @GuardedBy("mLock")
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return mDevCfgEnableContentProtectionReceiver;
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+ @NonNull Context context) {
+ return new ContentProtectionBlocklistManager(new ContentProtectionPackageManager(context));
+ }
+
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@Override
@@ -1075,14 +1111,21 @@
@GuardedBy("mGlobalWhitelistStateLock")
public ContentCaptureOptions getOptions(@UserIdInt int userId,
@NonNull String packageName) {
- boolean packageWhitelisted;
+ boolean isContentCaptureReceiverEnabled;
+ boolean isContentProtectionReceiverEnabled;
ArraySet<ComponentName> whitelistedComponents = null;
+
synchronized (mGlobalWhitelistStateLock) {
- packageWhitelisted = isWhitelisted(userId, packageName);
- if (!packageWhitelisted) {
- // Full package is not allowlisted: check individual components first
+ isContentCaptureReceiverEnabled =
+ isContentCaptureReceiverEnabled(userId, packageName);
+ isContentProtectionReceiverEnabled =
+ isContentProtectionReceiverEnabled(packageName);
+
+ if (!isContentCaptureReceiverEnabled) {
+ // Full package is not allowlisted: check individual components next
whitelistedComponents = getWhitelistedComponents(userId, packageName);
- if (whitelistedComponents == null
+ if (!isContentProtectionReceiverEnabled
+ && whitelistedComponents == null
&& packageName.equals(mServicePackages.get(userId))) {
// No components allowlisted either, but let it go because it's the
// service's own package
@@ -1101,7 +1144,9 @@
}
}
- if (!packageWhitelisted && whitelistedComponents == null) {
+ if (!isContentCaptureReceiverEnabled
+ && !isContentProtectionReceiverEnabled
+ && whitelistedComponents == null) {
// No can do!
if (verbose) {
Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1118,9 +1163,9 @@
mDevCfgTextChangeFlushingFrequencyMs,
mDevCfgLogHistorySize,
mDevCfgDisableFlushForViewTreeAppearing,
- /* enableReceiver= */ true,
+ isContentCaptureReceiverEnabled || whitelistedComponents != null,
new ContentCaptureOptions.ContentProtectionOptions(
- mDevCfgEnableContentProtectionReceiver,
+ isContentProtectionReceiverEnabled,
mDevCfgContentProtectionBufferSize),
whitelistedComponents);
if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
@@ -1141,6 +1186,35 @@
}
}
}
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+ return isContentCaptureReceiverEnabled(userId, packageName)
+ || isContentProtectionReceiverEnabled(packageName);
+ }
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+ return super.isWhitelisted(userId, componentName)
+ || isContentProtectionReceiverEnabled(componentName.getPackageName());
+ }
+
+ private boolean isContentCaptureReceiverEnabled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return super.isWhitelisted(userId, packageName);
+ }
+
+ private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+ if (mContentProtectionBlocklistManager == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (!mDevCfgEnableContentProtectionReceiver) {
+ return false;
+ }
+ }
+ return mContentProtectionBlocklistManager.isAllowed(packageName);
+ }
}
private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
index 715cf9a..a0fd28b 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -35,7 +35,7 @@
*
* @hide
*/
-class ContentProtectionBlocklistManager {
+public class ContentProtectionBlocklistManager {
private static final String TAG = "ContentProtectionBlocklistManager";
@@ -46,7 +46,7 @@
@Nullable private Set<String> mPackageNameBlocklist;
- protected ContentProtectionBlocklistManager(
+ public ContentProtectionBlocklistManager(
@NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
mContentProtectionPackageManager = contentProtectionPackageManager;
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 1847e5d..4ebac07 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -43,7 +43,7 @@
@NonNull private final PackageManager mPackageManager;
- ContentProtectionPackageManager(@NonNull Context context) {
+ public ContentProtectionPackageManager(@NonNull Context context) {
mPackageManager = context.getPackageManager();
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..7701bc6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@
maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
final int maxHeight =
maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ // We cannot determine cutouts and rounded corners of external displays.
+ if (mStaticDisplayInfo.isInternal) {
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ }
+
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ce4f445..1ae1e03 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1847,6 +1847,9 @@
td.setEnsureStatusBarContrastWhenTransparent(
atd.getEnsureStatusBarContrastWhenTransparent());
}
+ if (td.getStatusBarAppearance() == 0) {
+ td.setStatusBarAppearance(atd.getStatusBarAppearance());
+ }
if (td.getNavigationBarColor() == 0) {
td.setNavigationBarColor(atd.getNavigationBarColor());
td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a5b1548..b43209f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -14837,17 +14837,23 @@
"Caller is not managed profile owner or device owner;"
+ " only managed profile owner or device owner may control the preferential"
+ " network service");
- synchronized (getLockObject()) {
- final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
- caller.getUserId());
- if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
- preferentialNetworkServiceConfigs)) {
- requiredAdmin.mPreferentialNetworkServiceConfigs =
- new ArrayList<>(preferentialNetworkServiceConfigs);
- saveSettingsLocked(caller.getUserId());
+
+ try {
+ updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+ caller.getUserId());
+ if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
+ preferentialNetworkServiceConfigs)) {
+ requiredAdmin.mPreferentialNetworkServiceConfigs =
+ new ArrayList<>(preferentialNetworkServiceConfigs);
+ saveSettingsLocked(caller.getUserId());
+ }
}
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, "Failed to set preferential network service configs");
+ throw e;
}
- updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
.setBoolean(preferentialNetworkServiceConfigs
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf9..f89f73c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -1009,6 +1010,72 @@
0.001f);
}
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index c872a11..0e92d22 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -18,8 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -29,6 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
import com.android.server.pm.UserManagerInternal;
import com.google.common.collect.ImmutableList;
@@ -56,12 +65,21 @@
private static final String PACKAGE_NAME = "com.test.package";
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(PACKAGE_NAME, "TestClass");
+
private static final Context sContext = ApplicationProvider.getApplicationContext();
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private UserManagerInternal mMockUserManagerInternal;
+ @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+ private boolean mDevCfgEnableContentProtectionReceiver;
+
+ private int mContentProtectionBlocklistManagersCreated;
+
private ContentCaptureManagerService mContentCaptureManagerService;
@Before
@@ -69,46 +87,235 @@
when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
}
@Test
- public void getOptions_notAllowlisted() {
+ public void constructor_default_doesNotCreateContentProtectionBlocklistManager() {
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_flagDisabled_doesNotContentProtectionBlocklistManager() {
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_flagEnabled_createsContentProtectionBlocklistManager() {
+ mDevCfgEnableContentProtectionReceiver = true;
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+ }
+
+ @Test
+ public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+ mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+ verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
ContentCaptureOptions actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
USER_ID, PACKAGE_NAME);
assertThat(actual).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
}
@Test
- public void getOptions_allowlisted_contentCaptureReceiver() {
- mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
- USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+ public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
ContentCaptureOptions actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
USER_ID, PACKAGE_NAME);
assertThat(actual).isNotNull();
- assertThat(actual.enableReceiver).isTrue();
- assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
- assertThat(actual.whitelistedComponents).isNull();
- }
-
- @Test
- public void getOptions_allowlisted_bothReceivers() {
- mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
- mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
- USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
-
- ContentCaptureOptions actual =
- mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
- USER_ID, PACKAGE_NAME);
-
- assertThat(actual).isNotNull();
- assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.enableReceiver).isFalse();
+ assertThat(actual.contentProtectionOptions).isNotNull();
assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
assertThat(actual.whitelistedComponents).isNull();
}
+
+ @Test
+ public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+ assertThat(actual.whitelistedComponents).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+ assertThat(actual.whitelistedComponents).isNull();
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+ TestContentCaptureManagerService() {
+ super(sContext);
+ this.mDevCfgEnableContentProtectionReceiver =
+ ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+ @NonNull Context context) {
+ mContentProtectionBlocklistManagersCreated++;
+ return mMockContentProtectionBlocklistManager;
+ }
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 0000000..00316ea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import android.tools.common.datatypes.Rect
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity.
+ * Transitions:
+ * A start C with alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ // Launch a split
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ }
+ transitions {
+ // Launch C with alwaysExpand
+ testApp.launchAlwaysExpandActivity(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Transition begins with a split. */
+ @Presubmit
+ @Test
+ fun startsWithSplit() {
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+
+ /** Main activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** At the end of transition always expand activity is in fullscreen. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+ flicker.assertWmEnd {
+ this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Always expand activity is on top of the split. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityOnTop() {
+ flicker.assertWmEnd {
+ this.isAppWindowOnTop(
+ ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
+
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index e019b2b..a21965e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -75,6 +75,29 @@
.StateSyncBuilder()
.withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
.waitForAndVerify()
+ }
+
+ /**
+ * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+ * a fullscreen window on top of the visible region.
+ */
+ fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(
+ By.res(getPackage(),
+ "launch_always_expand_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) {
+ "Can't find launch always expand activity button on screen."
+ }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+ .waitForAndVerify()
}
/**
@@ -105,6 +128,9 @@
val SECONDARY_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+ val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9..dc9ff3b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -198,6 +198,13 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
+ android:name=".ActivityEmbeddingAlwaysExpandActivity"
+ android:label="ActivityEmbedding AlwaysExpand"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false"/>
+ <activity
android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a8..f5241ca 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -37,4 +37,12 @@
android:onClick="launchPlaceholderSplit"
android:text="Launch Placeholder Split" />
+ <Button
+ android:id="@+id/launch_always_expand_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchAlwaysExpandActivity"
+ android:text="Launch Always Expand Activity" />
+
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_embedding_base_layout);
+ findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+ }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2cc..6120254 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -23,6 +23,9 @@
import android.util.Log;
import android.view.View;
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.RuleController;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
import androidx.window.extensions.embedding.SplitPairRule;
@@ -30,6 +33,7 @@
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
+import java.util.HashSet;
import java.util.Set;
/** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
@@ -50,6 +54,23 @@
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
}
+ /** R.id.launch_always_expand_activity_button onClick */
+ public void launchAlwaysExpandActivity(View view) {
+ final Set<ActivityFilter> activityFilters = new HashSet<>();
+ activityFilters.add(
+ new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+ null));
+ final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+ .setAlwaysExpand(true)
+ .build();
+
+ RuleController rc = RuleController.getInstance(this);
+
+ rc.addRule(activityRule);
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+ }
+
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
initializeSplitRules(createSplitPlaceholderRules());
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 9c3226b..0f5c003 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -99,6 +99,12 @@
FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
}
+ public static class AlwaysExpandActivity {
+ public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+ }
+
public static class PlaceholderPrimaryActivity {
public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,