Merge "Fix HUN touchable area" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 50d97cf..5393475 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12886,7 +12886,14 @@
field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_CONTENT_RECOMMENDATION = 4; // 0x4
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_NEWS = 3; // 0x3
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_OTHER = 0; // 0x0
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_PROMOTION = 1; // 0x1
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_SOCIAL_MEDIA = 2; // 0x2
+ field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_UNKNOWN = -1; // 0xffffffff
}
public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c2f960f..d899511 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -379,6 +379,10 @@
method public void setImportantConversation(boolean);
method public void setOriginalImportance(int);
method public void setUserVisibleTaskShown(boolean);
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String NEWS_ID = "android.app.news";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String PROMOTIONS_ID = "android.app.promotions";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String RECS_ID = "android.app.recs";
+ field @FlaggedApi("android.service.notification.notification_classification") public static final String SOCIAL_MEDIA_ID = "android.app.social";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3f6c81b..326d7ce 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -72,6 +72,35 @@
public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
/**
+ * A reserved id for a system channel reserved for promotional notifications.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String PROMOTIONS_ID = "android.app.promotions";
+ /**
+ * A reserved id for a system channel reserved for non-conversation social media notifications.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String SOCIAL_MEDIA_ID = "android.app.social";
+ /**
+ * A reserved id for a system channel reserved for news notifications.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String NEWS_ID = "android.app.news";
+ /**
+ * A reserved id for a system channel reserved for content recommendation notifications.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String RECS_ID = "android.app.recs";
+
+ /**
* The formatter used by the system to create an id for notification
* channels when it automatically creates conversation channels on behalf of an app. The format
* string takes two arguments, in this order: the
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index fdd8b04..678bd6b 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -137,6 +137,7 @@
BIOMETRIC_WEAK,
BIOMETRIC_CONVENIENCE,
DEVICE_CREDENTIAL,
+ MANDATORY_BIOMETRICS,
})
@Retention(RetentionPolicy.SOURCE)
@interface Types {}
@@ -214,6 +215,21 @@
*/
int DEVICE_CREDENTIAL = 1 << 15;
+ /**
+ * The bit is used to request for mandatory biometrics.
+ *
+ * <p> The requirements to trigger mandatory biometrics are as follows:
+ * 1. User must have enabled the toggle for mandatory biometrics is settings
+ * 2. User must have enrollments for all {@link #BIOMETRIC_STRONG} sensors available
+ * 3. The device must not be in a trusted location
+ * </p>
+ *
+ * <p> If all the above conditions are satisfied, only {@link #BIOMETRIC_STRONG} sensors
+ * will be eligible for authentication, and device credential fallback will be dropped.
+ * @hide
+ */
+ int MANDATORY_BIOMETRICS = 1 << 16;
+
}
/**
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 37a2df8..42f5fc8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -794,10 +794,15 @@
public void onDialogDismissed(int reason) {
// Check the reason and invoke OnClickListener(s) if necessary
if (reason == DISMISSED_REASON_NEGATIVE) {
- mNegativeButtonInfo.executor.execute(() -> {
- mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
- mIsPromptShowing = false;
- });
+ if (mNegativeButtonInfo != null) {
+ mNegativeButtonInfo.executor.execute(() -> {
+ mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+ mIsPromptShowing = false;
+ });
+ } else {
+ mAuthenticationCallback.onAuthenticationError(BIOMETRIC_ERROR_USER_CANCELED,
+ null /* errString */);
+ }
} else if (reason == DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS) {
if (mContentViewMoreOptionsButtonInfo != null) {
mContentViewMoreOptionsButtonInfo.executor.execute(() -> {
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index ba9f30d..901f6b7 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -195,6 +195,10 @@
return true;
} else if (mContentView != null && isContentViewMoreOptionsButtonUsed()) {
return true;
+ } else if (Flags.mandatoryBiometrics()
+ && (mAuthenticators & BiometricManager.Authenticators.MANDATORY_BIOMETRICS)
+ != 0) {
+ return true;
}
return false;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d91508f..4e8a04f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11046,6 +11046,12 @@
public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
/**
+ * Whether or not mandatory biometrics is enabled.
+ * @hide
+ */
+ public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics";
+
+ /**
* Whether or not active unlock triggers on wake.
* @hide
*/
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9c14946..63410c7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,6 +15,8 @@
*/
package android.service.notification;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
@@ -62,7 +64,8 @@
KEY_IMPORTANCE_PROPOSAL,
KEY_SENSITIVE_CONTENT,
KEY_RANKING_SCORE,
- KEY_NOT_CONVERSATION
+ KEY_NOT_CONVERSATION,
+ KEY_TYPE
})
@Retention(RetentionPolicy.SOURCE)
public @interface Keys {}
@@ -172,6 +175,59 @@
public static final String KEY_NOT_CONVERSATION = "key_not_conversation";
/**
+ * Data type: int, the classification type of this notification. The OS may display
+ * notifications differently depending on the type, and may change the alerting level of the
+ * notification.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final String KEY_TYPE = "key_type";
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_UNKNOWN,
+ TYPE_OTHER,
+ TYPE_PROMOTION,
+ TYPE_SOCIAL_MEDIA,
+ TYPE_NEWS,
+ TYPE_CONTENT_RECOMMENDATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Types {}
+
+ /**
+ * The type of this notification is unknown.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_UNKNOWN = -1;
+ /**
+ * The type of this notification is not one of ones known to the NotificationAssistantService.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_OTHER = 0;
+ /**
+ * The type of this notification is a promotion/deal.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_PROMOTION = 1;
+ /**
+ * The type of this notification is social media content that isn't a
+ * {@link Notification.Builder#setShortcutId(String) conversation}.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_SOCIAL_MEDIA = 2;
+ /**
+ * The type of this notification is news.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_NEWS = 3;
+ /**
+ * The type of this notification is content recommendation, for example new videos or books the
+ * user may be interested in.
+ */
+ @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public static final int TYPE_CONTENT_RECOMMENDATION = 4;
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index c5b4b41..bdef041 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -35,4 +35,12 @@
description: "Guards the new CallStyleNotificationEventsCallback"
bug: "305095040"
is_fixed_read_only: true
+}
+
+flag {
+ name: "notification_classification"
+ is_exported: true
+ namespace: "systemui"
+ description: "Allows the NAS to classify notifications"
+ bug: "343988084"
}
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b6e8383..351cbad 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5430,6 +5430,10 @@
<string name="call_notification_screening_text">Screening an incoming call</string>
<string name="default_notification_channel_label">Uncategorized</string>
+ <string name="promotional_notification_channel_label">Promotions</string>
+ <string name="social_notification_channel_label">Social</string>
+ <string name="news_notification_channel_label">News</string>
+ <string name="recs_notification_channel_label">Recommendations</string>
<string name="importance_from_user">You set the importance of these notifications.</string>
<string name="importance_from_person">This is important because of the people involved.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 85397fa..0de9179 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5516,6 +5516,12 @@
<java-symbol type="string" name="biometric_dangling_notification_action_set_up" />
<java-symbol type="string" name="biometric_dangling_notification_action_not_now" />
+ <!-- Notification bundles -->
+ <java-symbol type="string" name="promotional_notification_channel_label"/>
+ <java-symbol type="string" name="social_notification_channel_label"/>
+ <java-symbol type="string" name="news_notification_channel_label"/>
+ <java-symbol type="string" name="recs_notification_channel_label"/>
+
<!-- Priority Modes icons -->
<java-symbol type="drawable" name="ic_zen_mode_type_bedtime" />
<java-symbol type="drawable" name="ic_zen_mode_type_driving" />
@@ -5526,5 +5532,4 @@
<java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" />
<java-symbol type="drawable" name="ic_zen_mode_type_theater" />
<java-symbol type="drawable" name="ic_zen_mode_type_unknown" />
-
</resources>
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
index ca91542..5464ea3 100644
--- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -209,6 +210,25 @@
"The number of list items exceeds ");
}
+ @Test
+ public void testOnDialogDismissed_dialogDismissedNegative() throws RemoteException {
+ final ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+ ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+ final BiometricPrompt.AuthenticationCallback callback =
+ mock(BiometricPrompt.AuthenticationCallback.class);
+ mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+ mLooper.dispatchAll();
+
+ verify(mService).authenticate(any(), anyLong(), anyInt(),
+ biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+ biometricServiceReceiverCaptor.getValue().onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+
+ verify(callback).onAuthenticationError(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+ null /* errString */);
+ }
+
private String generateRandomString(int charNum) {
final Random random = new Random();
final StringBuilder longString = new StringBuilder(charNum);
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index 7a4b6bc..99911de 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -69,5 +69,6 @@
<permission name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" />
<permission name="android.permission.SATELLITE_COMMUNICATION" />
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+ <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml
index 2401d4a..3b35f44 100644
--- a/data/etc/package-shareduid-allowlist.xml
+++ b/data/etc/package-shareduid-allowlist.xml
@@ -32,4 +32,5 @@
<config>
<allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" />
+ <allow-package-shareduid package="com.android.performanceLaunch" shareduid="com.android.performanceapp.tests" />
</config>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 9192e6e..fbc11c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -159,13 +159,24 @@
}
companion object {
+ /**
+ * Describes a task position and dimensions.
+ *
+ * @property instanceId instance id of the task
+ * @property uid uid of the app associated with the task
+ * @property taskHeight height of the task in px
+ * @property taskWidth width of the task in px
+ * @property taskX x-coordinate of the top-left corner
+ * @property taskY y-coordinate of the top-left corner
+ *
+ */
data class TaskUpdate(
val instanceId: Int,
val uid: Int,
- val taskHeight: Int = Int.MIN_VALUE,
- val taskWidth: Int = Int.MIN_VALUE,
- val taskX: Int = Int.MIN_VALUE,
- val taskY: Int = Int.MIN_VALUE,
+ val taskHeight: Int,
+ val taskWidth: Int,
+ val taskX: Int,
+ val taskY: Int,
)
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index 641952b..42ed988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -228,6 +228,7 @@
* Log the appropriate log event based on the new state of TasksInfos and previously cached
* state and update it
*/
+ // TODO(b/326231724): Trigger logging when task size or position is changed.
private fun identifyLogEventAndUpdateState(
transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
@@ -282,7 +283,6 @@
visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
}
- // TODO(b/326231724) - Add logging around taskInfoChanges Updates
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
sessionId: Int,
@@ -304,13 +304,17 @@
}
}
- // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
- val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
- // add task x, y if available
- taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
-
- return taskUpdate
+ val screenBounds = taskInfo.configuration.windowConfiguration.bounds
+ val positionInParent = taskInfo.positionInParent
+ return TaskUpdate(
+ instanceId = taskInfo.taskId,
+ uid = taskInfo.userId,
+ taskHeight = screenBounds.height(),
+ taskWidth = screenBounds.width(),
+ taskX = positionInParent.x,
+ taskY = positionInParent.y,
+ )
}
/** Get [EnterReason] for this session enter */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index fb03f20..cead21b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -19,6 +19,8 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
@@ -36,11 +38,12 @@
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
@@ -53,22 +56,23 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.same
+import org.mockito.kotlin.spy
import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
/**
* Test class for {@link DesktopModeLoggerTransitionObserver}
@@ -77,20 +81,19 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class DesktopModeLoggerTransitionObserverTest {
+class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
@JvmField
@Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeEventLogger::class.java)
- .mockStatic(DesktopModeStatus::class.java)
- .build()!!
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeStatus::class.java)
+ .build()!!
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock private lateinit var mockShellInit: ShellInit
- @Mock private lateinit var transitions: Transitions
- @Mock private lateinit var context: Context
+ private val testExecutor = mock<ShellExecutor>()
+ private val mockShellInit = mock<ShellInit>()
+ private val transitions = mock<Transitions>()
+ private val context = mock<Context>()
private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var shellInit: ShellInit
@@ -98,9 +101,9 @@
@Before
fun setup() {
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- shellInit = Mockito.spy(ShellInit(testExecutor))
- desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ shellInit = spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock<DesktopModeEventLogger>()
transitionObserver =
DesktopModeLoggerTransitionObserver(
@@ -121,7 +124,7 @@
@Test
fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
@@ -132,22 +135,17 @@
@Test
fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FREEFORM_INTENT))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
// task change is finalised when drag ends
val transitionInfo =
TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
@@ -155,82 +153,57 @@
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_DRAG))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_MENU_BUTTON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.KEYBOARD_SHORTCUT_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE)
}
@Test
fun transitToFront_logTaskAddedAndEnterReasonOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -238,35 +211,29 @@
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_TO_FRONT
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -274,35 +241,29 @@
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_CHANGE
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -310,35 +271,29 @@
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// Enter desktop mode from cancelled recents has no transition. Enter is detected on the
// next transition involving freeform windows
// TRANSIT_OPEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
@@ -349,243 +304,202 @@
// previous exit to overview transition
val previousSessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
val previousTransitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
.build()
callOnTransitionReady(previousTransitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ verifyTaskRemovedAndExitLogging(
+ previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
// TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE)
}
@Test
fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.SCREEN_ON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
+ verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
}
@Test
- fun transitSleep_logTaskAddedAndExitReasonScreenOff_sessionIdNull() {
+ fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.SCREEN_OFF))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.DRAG_TO_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
.addChange(change)
.build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.KEYBOARD_SHORTCUT_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.UNKNOWN_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE)
}
@Test
fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// recents transition
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
val transitionInfo =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() {
val sessionId = 1
// add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// task closing
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.TASK_FINISHED))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE)
}
@Test
fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
val sessionId = 1
// add a freeform task to an existing session
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
transitionObserver.setLoggerSessionId(sessionId)
// recents transition sent freeform window to back
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_TO_BACK, taskInfo)
val transitionInfo1 =
TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
callOnTransitionReady(transitionInfo1)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ verifyTaskRemovedAndExitLogging(
+ sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
callOnTransitionReady(transitionInfo2)
- verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
- verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
}
@Test
fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
val sessionId = 1
// add an existing freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
transitionObserver.setLoggerSessionId(sessionId)
// new freeform task added
- val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
}
@@ -593,42 +507,86 @@
fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
val sessionId = 1
// add two existing freeform tasks
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
transitionObserver.setLoggerSessionId(sessionId)
- // new freeform task added
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ // new freeform task closed
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
callOnTransitionReady(transitionInfo)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
}
/** Simulate calling the onTransitionReady() method */
private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
- val transition = mock(IBinder::class.java)
- val startT = mock(SurfaceControl.Transaction::class.java)
- val finishT = mock(SurfaceControl.Transaction::class.java)
+ val transition = mock<IBinder>()
+ val startT = mock<SurfaceControl.Transaction>()
+ val finishT = mock<SurfaceControl.Transaction>()
transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
}
- companion object {
- fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
- val taskInfo = ActivityManager.RunningTaskInfo()
- taskInfo.taskId = taskId
- taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+ private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) {
+ val sessionId = transitionObserver.getLoggerSessionId()
+ assertNotNull(sessionId)
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
- return taskInfo
- }
+ private fun verifyTaskRemovedAndExitLogging(
+ sessionId: Int,
+ exitReason: ExitReason,
+ taskUpdate: TaskUpdate
+ ) {
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate))
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason))
+ verifyZeroInteractions(desktopModeEventLogger)
+ assertNull(transitionObserver.getLoggerSessionId())
+ }
+
+ private companion object {
+ const val DEFAULT_TASK_ID = 1
+ const val DEFAULT_TASK_UID = 2
+ const val DEFAULT_TASK_HEIGHT = 100
+ const val DEFAULT_TASK_WIDTH = 200
+ const val DEFAULT_TASK_X = 30
+ const val DEFAULT_TASK_Y = 70
+ val DEFAULT_TASK_UPDATE =
+ TaskUpdate(
+ DEFAULT_TASK_ID,
+ DEFAULT_TASK_UID,
+ DEFAULT_TASK_HEIGHT,
+ DEFAULT_TASK_WIDTH,
+ DEFAULT_TASK_X,
+ DEFAULT_TASK_Y,
+ )
+
+ fun createTaskInfo(
+ windowMode: Int,
+ id: Int = DEFAULT_TASK_ID,
+ uid: Int = DEFAULT_TASK_UID,
+ taskHeight: Int = DEFAULT_TASK_HEIGHT,
+ taskWidth: Int = DEFAULT_TASK_WIDTH,
+ taskX: Int = DEFAULT_TASK_X,
+ taskY: Int = DEFAULT_TASK_Y,
+ ) = ActivityManager.RunningTaskInfo().apply {
+ taskId = id
+ userId = uid
+ configuration.windowConfiguration.apply {
+ windowingMode = windowMode
+ positionInParent = Point(taskX, taskY)
+ bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight))
+ }
+ }
fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
val change =
- Change(
- WindowContainerToken(mock(IWindowContainerToken::class.java)),
- mock(SurfaceControl::class.java))
+ Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>())
change.mode = mode
change.taskInfo = taskInfo
return change
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index f98908c..864de87 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -568,6 +568,8 @@
updateSingleDeviceUi();
+ if (mRequest.isSkipPrompt()) return;
+
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
mButtonNotAllow.setVisibility(View.VISIBLE);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index e809433..2a8ce87 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -127,6 +127,7 @@
return false;
}
}
+ sScanResultsLiveData.setValue(Collections.emptyList());
requireNonNull(associationRequest);
final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
intent.setAction(ACTION_START_DISCOVERY);
@@ -192,7 +193,6 @@
sDiscoveryStarted = true;
}
mStopAfterFirstMatch = request.isSingleDevice();
- sScanResultsLiveData.setValue(Collections.emptyList());
sStateLiveData.setValue(DiscoveryState.IN_PROGRESS);
final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 9f2ab69..8d02dbd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -279,5 +279,6 @@
Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS,
Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS,
Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS,
+ Settings.Secure.MANDATORY_BIOMETRICS,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cb7ac45..7169cf7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -438,5 +438,6 @@
VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR);
VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1));
}
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0fdcc7a..ffa1db3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -421,10 +421,13 @@
}
flag {
- name: "fast_unlock_transition"
+ name: "faster_unlock_transition"
namespace: "systemui"
description: "Faster wallpaper unlock transition"
bug: "298186160"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index fbfe050..4dc801c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -602,10 +602,14 @@
removeEnabled: Boolean,
onRemoveClicked: () -> Unit,
setToolbarSize: (toolbarSize: IntSize) -> Unit,
- setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
+ setRemoveButtonCoordinates: (coordinates: LayoutCoordinates?) -> Unit,
onOpenWidgetPicker: () -> Unit,
onEditDone: () -> Unit
) {
+ if (!removeEnabled) {
+ // Clear any existing coordinates when remove is not enabled.
+ setRemoveButtonCoordinates(null)
+ }
val removeButtonAlpha: Float by
animateFloatAsState(
targetValue = if (removeEnabled) 1f else 0.5f,
@@ -645,7 +649,13 @@
contentPadding = Dimensions.ButtonPadding,
modifier =
Modifier.graphicsLayer { alpha = removeButtonAlpha }
- .onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ .onGloballyPositioned {
+ // It's possible for this callback to fire after remove has been
+ // disabled. Check enabled state before setting.
+ if (removeEnabled) {
+ setRemoveButtonCoordinates(it)
+ }
+ }
) {
Row(
horizontalArrangement =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index bb17024..837c292a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -543,7 +543,8 @@
_toScene = old._toScene,
userActionDistanceScope = old.userActionDistanceScope,
orientation = old.orientation,
- isUpOrLeft = old.isUpOrLeft
+ isUpOrLeft = old.isUpOrLeft,
+ lastDistance = old.lastDistance,
)
.apply {
_currentScene = old._currentScene
@@ -561,6 +562,7 @@
val userActionDistanceScope: UserActionDistanceScope,
override val orientation: Orientation,
override val isUpOrLeft: Boolean,
+ var lastDistance: Float = DistanceUnspecified,
) :
TransitionState.Transition(_fromScene.key, _toScene.key),
TransitionState.HasOverscrollProperties {
@@ -620,8 +622,6 @@
get() = distance().absoluteValue
}
- private var lastDistance = DistanceUnspecified
-
/** Whether [TransitionState.Transition.finish] was called on this transition. */
var isFinishing = false
private set
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index b925130..33063c8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -461,7 +461,7 @@
val transitionKey: TransitionKey? = null,
)
-interface UserActionDistance {
+fun interface UserActionDistance {
/**
* Return the **absolute** distance of the user action given the size of the scene we are
* animating from and the [orientation].
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index f532e2e..a6e52c2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -1195,4 +1195,23 @@
assertThat(transition).hasProgress(0f)
assertThat(transition).hasOverscrollSpec()
}
+
+ @Test
+ fun interceptingTransitionKeepsDistance() = runGestureTest {
+ var swipeDistance = 75f
+ layoutState.transitions = transitions {
+ from(SceneA, to = SceneB) { distance = UserActionDistance { _, _ -> swipeDistance } }
+ }
+
+ // Start transition.
+ val controller = onDragStarted(overSlop = -50f)
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
+
+ // Intercept the transition and change the swipe distance. The original distance and
+ // progress should be the same.
+ swipeDistance = 50f
+ controller.onDragStopped(0f)
+ onDragStartedImmediately()
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt
new file mode 100644
index 0000000..9601f20
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.app.StatusBarManager
+import android.provider.DeviceConfig
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.navigationModeController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.fakeDeviceConfigProxy
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.full.memberProperties
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
+class StatusBarStartableTest : SysuiTestCase() {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun testSpecs(): List<TestSpec> {
+ return listOf(
+ TestSpec(
+ id = 0,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 1,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 2,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = false,
+ isPowerGestureIntercepted = true,
+ isOccluded = false,
+ ),
+ ),
+ TestSpec(
+ id = 3,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = false,
+ ),
+ ),
+ TestSpec(
+ id = 4,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = true,
+ isFaceEnrolledAndEnabled = false,
+ ),
+ ),
+ TestSpec(
+ id = 5,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = true,
+ isFaceEnrolledAndEnabled = true,
+ ),
+ ),
+ TestSpec(
+ id = 6,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = true,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 7,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = false,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 8,
+ expectedFlags =
+ StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME,
+ Preconditions(
+ isForceHideHomeAndRecents = true,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 9,
+ expectedFlags =
+ StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = false,
+ isShowHomeOverLockscreen = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ )
+ }
+
+ @BeforeClass
+ @JvmStatic
+ fun setUpClass() {
+ val seenIds = mutableSetOf<Int>()
+ testSpecs().forEach { testSpec ->
+ assertWithMessage("Duplicate TestSpec id=${testSpec.id}")
+ .that(seenIds)
+ .doesNotContain(testSpec.id)
+ seenIds.add(testSpec.id)
+ }
+ }
+ }
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val statusBarServiceMock = kosmos.statusBarService
+ private val flagsCaptor = argumentCaptor<Int>()
+
+ private val navigationModeControllerMock = kosmos.navigationModeController
+ private var currentNavigationMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ set(value) {
+ field = value
+ modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(field) }
+ }
+
+ private val modeChangedListeners = mutableListOf<NavigationModeController.ModeChangedListener>()
+
+ private val underTest = kosmos.statusBarStartable
+
+ @JvmField @Parameter(0) var testSpec: TestSpec? = null
+
+ @Before
+ fun setUp() {
+ whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation ->
+ val listener = invocation.arguments[0] as NavigationModeController.ModeChangedListener
+ modeChangedListeners.add(listener)
+ currentNavigationMode
+ }
+
+ underTest.start()
+ }
+
+ @Test
+ fun test() =
+ testScope.runTest {
+ val preconditions = checkNotNull(testSpec).preconditions
+ preconditions.assertValid()
+
+ setUpWith(preconditions)
+
+ runCurrent()
+
+ verify(statusBarServiceMock, atLeastOnce())
+ .disableForUser(flagsCaptor.capture(), any(), any(), anyInt())
+ assertThat(flagsCaptor.lastValue).isEqualTo(checkNotNull(testSpec).expectedFlags)
+ }
+
+ /** Sets up the state to match what's specified in the given [preconditions]. */
+ private fun TestScope.setUpWith(
+ preconditions: Preconditions,
+ ) {
+ if (!preconditions.isKeyguardShowing) {
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
+ if (preconditions.isForceHideHomeAndRecents) {
+ whenIdle(Scenes.Bouncer)
+ } else if (preconditions.isKeyguardShowing) {
+ whenIdle(Scenes.Lockscreen)
+ } else {
+ whenIdle(Scenes.Gone)
+ }
+ runCurrent()
+
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = preconditions.isOccluded,
+ taskInfo = if (preconditions.isOccluded) mock() else null,
+ )
+
+ kosmos.fakeDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+ preconditions.isShowHomeOverLockscreen.toString(),
+ /* makeDefault= */ false,
+ )
+ kosmos.fakeExecutor.runAllReady()
+
+ currentNavigationMode =
+ if (preconditions.isGesturalMode) {
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ } else {
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ }
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ if (preconditions.isAuthenticationMethodSecure) {
+ AuthenticationMethodModel.Pin
+ } else {
+ AuthenticationMethodModel.None
+ }
+ )
+
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState =
+ if (preconditions.isPowerGestureIntercepted) WakefulnessState.AWAKE
+ else WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = preconditions.isPowerGestureIntercepted,
+ )
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(
+ preconditions.isFaceEnrolledAndEnabled
+ )
+
+ runCurrent()
+ }
+
+ /** Sets up an idle state on the given [on] scene. */
+ private fun whenIdle(on: SceneKey) {
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(on))
+ kosmos.sceneInteractor.changeScene(on, "")
+ }
+
+ data class Preconditions(
+ val isForceHideHomeAndRecents: Boolean = false,
+ val isKeyguardShowing: Boolean = true,
+ val isOccluded: Boolean = false,
+ val isPowerGestureIntercepted: Boolean = false,
+ val isShowHomeOverLockscreen: Boolean = false,
+ val isGesturalMode: Boolean = true,
+ val isAuthenticationMethodSecure: Boolean = true,
+ val isFaceEnrolledAndEnabled: Boolean = false,
+ ) {
+ override fun toString(): String {
+ // Only include values set to true:
+ return buildString {
+ append("(")
+ append(
+ Preconditions::class
+ .memberProperties
+ .filter { it.get(this@Preconditions) == true }
+ .joinToString(", ") { "${it.name}=true" }
+ )
+ append(")")
+ }
+ }
+
+ fun assertValid() {
+ assertWithMessage(
+ "isForceHideHomeAndRecents means that the bouncer is showing so keyguard must" +
+ " be showing"
+ )
+ .that(!isForceHideHomeAndRecents || isKeyguardShowing)
+ .isTrue()
+ assertWithMessage("Cannot be occluded if the keyguard isn't showing")
+ .that(!isOccluded || isKeyguardShowing)
+ .isTrue()
+ }
+ }
+
+ data class TestSpec(
+ val id: Int,
+ val expectedFlags: Int,
+ val preconditions: Preconditions,
+ ) {
+ override fun toString(): String {
+ return "id=$id, expected=$expectedFlags, preconditions=$preconditions"
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index cf9ca15..c9850f2 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -19,8 +19,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/alternate_bouncer"
- android:focusable="true"
- android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png b/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png
new file mode 100644
index 0000000..526b585
--- /dev/null
+++ b/packages/SystemUI/res/drawable/placeholder_touchpad_back_gesture.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png b/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png
new file mode 100644
index 0000000..cba2d20
--- /dev/null
+++ b/packages/SystemUI/res/drawable/placeholder_touchpad_tablet_back_gesture.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index fc4bf8a..e80ed26 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -22,5 +22,4 @@
android:layout_height="wrap_content"
app:lottie_autoPlay="true"
app:lottie_loop="true"
- app:lottie_rawRes="@raw/sfps_pulse"
- android:importantForAccessibility="no"/>
\ No newline at end of file
+ app:lottie_rawRes="@raw/sfps_pulse"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8381812..82dafc3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3577,6 +3577,12 @@
<string name="touchpad_tutorial_action_key_button">Action key</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_done_button">Done</string>
+ <!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_back_gesture_action_title">Go back</string>
+ <!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.</string>
+ <string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string>
+ <string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9cc4650..9578da4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -139,6 +139,11 @@
overlayView!!.visibility = View.INVISIBLE
Log.d(TAG, "show(): adding overlayView $overlayView")
windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+ overlayView!!.announceForAccessibility(
+ applicationContext.resources.getString(
+ R.string.accessibility_side_fingerprint_indicator_label
+ )
+ )
}
/** Hide the side fingerprint sensor indicator */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 40df6cec..46f802f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -96,7 +96,8 @@
run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
}
}
- } ?: run { Log.w(TAG, "No data in result.") }
+ }
+ ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -127,7 +128,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant),
+ .background(LocalAndroidColorScheme.current.onSecondaryFixed),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 00566c1..a7e2633 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -39,7 +39,7 @@
import com.android.internal.R
import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
-import com.android.systemui.Flags.fastUnlockTransition
+import com.android.systemui.Flags.fasterUnlockTransition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
@@ -363,9 +363,9 @@
}
with(wallpaperCannedUnlockAnimator) {
- duration = if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
else LAUNCHER_ICONS_ANIMATION_DURATION_MS
- interpolator = if (fastUnlockTransition()) Interpolators.LINEAR
+ interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
else Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(valueAnimator.animatedValue as Float)
@@ -613,7 +613,7 @@
val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
- val duration = if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ val duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
else LAUNCHER_ICONS_ANIMATION_DURATION_MS
listeners.forEach {
it.onUnlockAnimationStarted(
@@ -1148,7 +1148,7 @@
* TODO (b/298186160) replace references with the constant itself when flag is removed
*/
private fun cannedUnlockStartDelayMs(): Long {
- return if (fastUnlockTransition()) CANNED_UNLOCK_START_DELAY
+ return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
else LEGACY_CANNED_UNLOCK_START_DELAY
}
@@ -1157,7 +1157,7 @@
* TODO (b/298186160) replace references with the constant itself when flag is removed
*/
private fun unlockAnimationDurationMs(): Long {
- return if (fastUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
else LEGACY_UNLOCK_ANIMATION_DURATION_MS
}
@@ -1166,7 +1166,7 @@
* TODO (b/298186160) replace references with the constant itself when flag is removed
*/
private fun surfaceBehindFadeOutDurationMs(): Long {
- return if (fastUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS
+ return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS
else LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
}
@@ -1175,7 +1175,7 @@
* TODO (b/298186160) replace references with the constant itself when flag is removed
*/
private fun surfaceBehindFadeOutStartDelayMs(): Long {
- return if (fastUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS
+ return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS
else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2d60fcc..b70dbe2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -151,6 +151,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -178,6 +179,8 @@
import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -187,8 +190,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import kotlinx.coroutines.CoroutineDispatcher;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -3502,12 +3503,14 @@
+ " --> flags=0x" + Integer.toHexString(flags));
}
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to set disable flags: " + flags, e);
+ if (!SceneContainerFlag.isEnabled()) {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to set disable flags: " + flags, e);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 6550937..f8063c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -86,7 +86,10 @@
privateFlags =
WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ // Avoid announcing window title.
+ accessibilityTitle = " "
}
+
private var alternateBouncerView: ConstraintLayout? = null
override fun start() {
@@ -304,6 +307,7 @@
}
}
}
+
companion object {
private const val TAG = "AlternateBouncerViewBinder"
private const val swipeTag = "AlternateBouncer-SWIPE"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 8b2f619..eab0d48 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -354,10 +354,21 @@
activeDevice =
routingSession?.let {
+ val icon = if (it.selectedRoutes.size > 1) {
+ context.getDrawable(
+ com.android.settingslib.R.drawable.ic_media_group_device)
+ } else {
+ connectedDevice?.icon // Single route. We don't change the icon.
+ }
// For a remote session, always use the current device from
- // LocalMediaManager. Override with routing session name if available to
- // show dynamic group name.
- connectedDevice?.copy(name = it.name ?: connectedDevice.name)
+ // LocalMediaManager. Override with routing session information if
+ // available:
+ // - Name: To show the dynamic group name.
+ // - Icon: To show the group icon if there's more than one selected
+ // route.
+ connectedDevice?.copy(
+ name = it.name ?: connectedDevice.name,
+ icon = icon)
} ?: MediaDeviceData(
enabled = false,
icon = context.getDrawable(R.drawable.ic_media_home_devices),
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 1e86563..b48b409 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -85,6 +85,7 @@
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.dialog.MediaItem.MediaItemType;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
@@ -110,6 +111,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -661,28 +663,38 @@
/* connectedMediaDevice */ null,
devices,
needToHandleMutingExpectedDevice);
+ } else {
+ // selected device exist
+ return categorizeMediaItemsLocked(
+ connectedMediaDevice,
+ devices,
+ /* needToHandleMutingExpectedDevice */ false);
}
- // selected device exist
- return categorizeMediaItemsLocked(
- connectedMediaDevice,
- devices,
- /* needToHandleMutingExpectedDevice */ false);
}
// To keep the same list order
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
final Map<Integer, MediaItem> dividerItems = new HashMap<>();
+
+ Map<String, MediaDevice> idToMediaDeviceMap =
+ devices.stream()
+ .collect(Collectors.toMap(MediaDevice::getId, Function.identity()));
+
for (MediaItem originalMediaItem : oldMediaItems) {
- for (MediaDevice newDevice : devices) {
- if (originalMediaItem.getMediaDevice().isPresent()
- && TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(),
- newDevice.getId())) {
- targetMediaDevices.add(newDevice);
- break;
+ switch (originalMediaItem.getMediaItemType()) {
+ case MediaItemType.TYPE_GROUP_DIVIDER -> {
+ dividerItems.put(
+ oldMediaItems.indexOf(originalMediaItem), originalMediaItem);
}
- }
- if (originalMediaItem.getMediaItemType()
- == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
- dividerItems.put(oldMediaItems.indexOf(originalMediaItem), originalMediaItem);
+ case MediaItemType.TYPE_DEVICE -> {
+ String originalMediaItemId =
+ originalMediaItem.getMediaDevice().orElseThrow().getId();
+ if (idToMediaDeviceMap.containsKey(originalMediaItemId)) {
+ targetMediaDevices.add(idToMediaDeviceMap.get(originalMediaItemId));
+ }
+ }
+ case MediaItemType.TYPE_PAIR_NEW_DEVICE -> {
+ // Do nothing.
+ }
}
}
if (targetMediaDevices.size() != devices.size()) {
@@ -723,12 +735,10 @@
finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
} else {
if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
- attachGroupDivider(finalMediaItems, mContext.getString(
- R.string.media_output_group_title_suggested_device));
+ addSuggestedDeviceGroupDivider(finalMediaItems);
suggestedDeviceAdded = true;
} else if (!device.isSuggestedDevice() && !displayGroupAdded) {
- attachGroupDivider(finalMediaItems, mContext.getString(
- R.string.media_output_group_title_speakers_and_displays));
+ addSpeakersAndDisplaysGroupDivider(finalMediaItems);
displayGroupAdded = true;
}
finalMediaItems.add(MediaItem.createDeviceMediaItem(device));
@@ -738,8 +748,17 @@
return finalMediaItems;
}
- private void attachGroupDivider(List<MediaItem> mediaItems, String title) {
- mediaItems.add(MediaItem.createGroupDividerMediaItem(title));
+ private void addSuggestedDeviceGroupDivider(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(R.string.media_output_group_title_suggested_device)));
+ }
+
+ private void addSpeakersAndDisplaysGroupDivider(List<MediaItem> mediaItems) {
+ mediaItems.add(
+ MediaItem.createGroupDividerMediaItem(
+ mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays)));
}
private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 323ca87..08462d7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -25,6 +25,7 @@
import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
+import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -66,6 +67,11 @@
@Binds
@IntoMap
+ @ClassKey(StatusBarStartable::class)
+ fun statusBarStartable(impl: StatusBarStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 4691eba..17dc9a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
+import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -72,6 +73,11 @@
@Binds
@IntoMap
+ @ClassKey(StatusBarStartable::class)
+ fun statusBarStartable(impl: StatusBarStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
new file mode 100644
index 0000000..893f030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.annotation.SuppressLint
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.IBinder
+import android.os.RemoteException
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.compose.animation.scene.SceneKey
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.navigation.domain.interactor.NavigationInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class StatusBarStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val applicationContext: Context,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
+ private val deviceConfigInteractor: DeviceConfigInteractor,
+ private val navigationInteractor: NavigationInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val powerInteractor: PowerInteractor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val statusBarService: IStatusBarService,
+) : CoreStartable {
+
+ private val disableToken: IBinder = Binder()
+
+ override fun start() {
+ if (!SceneContainerFlag.isEnabled) {
+ return
+ }
+
+ applicationScope.launch {
+ combine(
+ selectedUserInteractor.selectedUser,
+ sceneInteractor.currentScene,
+ deviceEntryInteractor.isDeviceEntered,
+ sceneContainerOcclusionInteractor.invisibleDueToOcclusion,
+ deviceConfigInteractor.property(
+ namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
+ name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+ default = true,
+ ),
+ navigationInteractor.isGesturalMode,
+ authenticationInteractor.authenticationMethod,
+ powerInteractor.detailedWakefulness,
+ ) { values ->
+ val selectedUserId = values[0] as Int
+ val currentScene = values[1] as SceneKey
+ val isDeviceEntered = values[2] as Boolean
+ val isOccluded = values[3] as Boolean
+ val isShowHomeOverLockscreen = values[4] as Boolean
+ val isGesturalMode = values[5] as Boolean
+ val authenticationMethod = values[6] as AuthenticationMethodModel
+ val wakefulnessModel = values[7] as WakefulnessModel
+
+ val isForceHideHomeAndRecents = currentScene == Scenes.Bouncer
+ val isKeyguardShowing = !isDeviceEntered
+ val isPowerGestureIntercepted =
+ with(wakefulnessModel) {
+ isAwake() &&
+ powerButtonLaunchGestureTriggered &&
+ lastSleepReason == WakeSleepReason.POWER_BUTTON
+ }
+
+ var flags = StatusBarManager.DISABLE_NONE
+
+ if (isForceHideHomeAndRecents || (isKeyguardShowing && !isOccluded)) {
+ if (!isShowHomeOverLockscreen || !isGesturalMode) {
+ flags = flags or StatusBarManager.DISABLE_HOME
+ }
+ flags = flags or StatusBarManager.DISABLE_RECENT
+ }
+
+ if (
+ isPowerGestureIntercepted &&
+ isOccluded &&
+ authenticationMethod.isSecure &&
+ deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+ ) {
+ flags = flags or StatusBarManager.DISABLE_RECENT
+ }
+
+ selectedUserId to flags
+ }
+ .distinctUntilChanged()
+ .collect { (selectedUserId, flags) ->
+ @SuppressLint("WrongConstant", "NonInjectedService")
+ if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
+ Log.w(TAG, "Could not get status bar manager")
+ return@collect
+ }
+
+ withContext(backgroundDispatcher) {
+ try {
+ statusBarService.disableForUser(
+ flags,
+ disableToken,
+ applicationContext.packageName,
+ selectedUserId,
+ )
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Failed to set disable flags: $flags", e)
+ }
+ }
+ }
+ }
+ }
+
+ override fun onBootCompleted() {
+ applicationScope.launch(backgroundDispatcher) {
+ try {
+ statusBarService.disableForUser(
+ StatusBarManager.DISABLE_NONE,
+ disableToken,
+ applicationContext.packageName,
+ selectedUserInteractor.getSelectedUserId(true),
+ )
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Failed to clear flags", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "StatusBarStartable"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
index 5adf31b..f166d32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt
@@ -13,12 +13,18 @@
/** The subset of active listeners which are temporary (will be removed after called) */
private val temporaryListeners = ArraySet<OnReorderingAllowedListener>()
+ private val banListeners = ListenerSet<OnReorderingBannedListener>()
+
var isReorderingAllowed = true
set(value) {
if (field != value) {
field = value
if (value) {
notifyReorderingAllowed()
+ } else {
+ banListeners.forEach { listener ->
+ listener.onReorderingBanned()
+ }
}
}
}
@@ -38,6 +44,10 @@
allListeners.addIfAbsent(listener)
}
+ fun addPersistentReorderingBannedListener(listener: OnReorderingBannedListener) {
+ banListeners.addIfAbsent(listener)
+ }
+
/** Add a listener which will be removed when it is called. */
fun addTemporaryReorderingAllowedListener(listener: OnReorderingAllowedListener) {
// Only add to the temporary set if it was added to the global set
@@ -57,3 +67,8 @@
fun interface OnReorderingAllowedListener {
fun onReorderingAllowed()
}
+
+fun interface OnReorderingBannedListener {
+ fun onReorderingBanned()
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3925beb..c5dcb09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
@@ -86,7 +87,7 @@
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
private final VisualStabilityProvider mVisualStabilityProvider;
- private final AvalancheController mAvalancheController;
+ private AvalancheController mAvalancheController;
// TODO(b/328393698) move the topHeadsUpRow logic to an interactor
private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
@@ -175,6 +176,7 @@
javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
this::onShadeOrQsExpanded);
}
+ mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener);
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -382,6 +384,8 @@
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+ mAvalancheController.setEnableAtRuntime(true);
+
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
@@ -392,6 +396,28 @@
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
};
+ private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+ if (mAvalancheController != null) {
+ // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus
+ // seen in open shade; clear them so we don't show them again when the shade closes and
+ // reordering is allowed again.
+ mAvalancheController.logDroppedHuns(mAvalancheController.getWaitingKeys().size());
+ mAvalancheController.clearNext();
+
+ // In open shade the first HUN is pinned, and visual stability logic prevents us from
+ // unpinning this first HUN as long as the shade remains open. AvalancheController only
+ // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+ // throttling here so that the incoming HUN stream is not forever paused. This is reset
+ // when reorder becomes allowed.
+ mAvalancheController.setEnableAtRuntime(false);
+
+ // Note that we cannot do the above when
+ // 1) the remove runnable runs because its delay means it may not run before shade close
+ // 2) reordering is allowed again (when shade closes) because the HUN appear animation
+ // will have started by then
+ }
+ };
+
///////////////////////////////////////////////////////////////////////////////////////////////
// HeadsUpManager utility (protected) methods overrides:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 32774e0..dbe54f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -39,6 +39,7 @@
private val tag = "AvalancheController"
private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
+ var enableAtRuntime = true
// HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
@VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -81,13 +82,17 @@
dumpManager.registerNormalDumpable(tag, /* module */ this)
}
+ fun isEnabled() : Boolean {
+ return NotificationThrottleHun.isEnabled && enableAtRuntime
+ }
+
fun getShowingHunKey(): String {
return getKey(headsUpEntryShowing)
}
/** Run or delay Runnable for given HeadsUpEntry */
fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
runnable.run()
return
}
@@ -143,7 +148,7 @@
* all Runnables associated with that entry.
*/
fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
runnable.run()
return
}
@@ -184,7 +189,7 @@
* BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
*/
fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
}
@@ -233,7 +238,7 @@
/** Return true if entry is waiting to show. */
fun isWaiting(key: String): Boolean {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return false
}
for (entry in nextMap.keys) {
@@ -246,7 +251,7 @@
/** Return list of keys for huns waiting */
fun getWaitingKeys(): MutableList<String> {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return mutableListOf()
}
val keyList = mutableListOf<String>()
@@ -257,7 +262,7 @@
}
fun getWaitingEntry(key: String): HeadsUpEntry? {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return null
}
for (headsUpEntry in nextMap.keys) {
@@ -269,7 +274,7 @@
}
fun getWaitingEntryList(): List<HeadsUpEntry> {
- if (!NotificationThrottleHun.isEnabled) {
+ if (!isEnabled()) {
return mutableListOf()
}
return nextMap.keys.toList()
@@ -310,11 +315,7 @@
// Remove runnable labels for dropped huns
val listToDrop = nextList.subList(1, nextList.size)
-
- // Log dropped HUNs
- for (e in listToDrop) {
- uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
- }
+ logDroppedHuns(listToDrop.size)
if (debug) {
// Clear runnable labels
@@ -331,6 +332,12 @@
showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
}
+ fun logDroppedHuns(numDropped: Int) {
+ for (n in 1..numDropped) {
+ uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+ }
+ }
+
fun clearNext() {
nextList.clear()
nextMap.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt
new file mode 100644
index 0000000..2460761c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/BackGestureTutorialScreen.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.view
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.systemui.res.R
+
+@Composable
+fun BackGestureTutorialScreen(
+ onDoneButtonClicked: () -> Unit,
+ onBack: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ BackHandler { onBack() }
+ Column(
+ verticalArrangement = Arrangement.Center,
+ modifier =
+ modifier
+ .background(color = MaterialTheme.colorScheme.surfaceContainer)
+ .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
+ .fillMaxSize()
+ ) {
+ Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
+ TutorialDescription(modifier = Modifier.weight(1f))
+ Spacer(modifier = Modifier.width(76.dp))
+ TutorialAnimation(modifier = Modifier.weight(1f).padding(top = 24.dp))
+ }
+ DoneButton(onDoneButtonClicked = onDoneButtonClicked)
+ }
+}
+
+@Composable
+fun TutorialDescription(modifier: Modifier = Modifier) {
+ Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
+ Text(
+ text = stringResource(id = R.string.touchpad_back_gesture_action_title),
+ style = MaterialTheme.typography.displayLarge
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = stringResource(id = R.string.touchpad_back_gesture_guidance),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+fun TutorialAnimation(modifier: Modifier = Modifier) {
+ // below are just placeholder images, will be substituted by animations soon
+ Column(modifier = modifier.fillMaxWidth()) {
+ Image(
+ painter = painterResource(id = R.drawable.placeholder_touchpad_tablet_back_gesture),
+ contentDescription =
+ stringResource(
+ id = R.string.touchpad_back_gesture_screen_animation_content_description
+ ),
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ Image(
+ painter = painterResource(id = R.drawable.placeholder_touchpad_back_gesture),
+ contentDescription =
+ stringResource(id = R.string.touchpad_back_gesture_animation_content_description),
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index b7629c7..bbd50ef 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -27,7 +27,6 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.compose.theme.PlatformTheme
-import com.android.systemui.touchpad.tutorial.ui.BackGestureTutorialViewModel
import com.android.systemui.touchpad.tutorial.ui.GestureViewModelFactory
import com.android.systemui.touchpad.tutorial.ui.HomeGestureTutorialViewModel
import com.android.systemui.touchpad.tutorial.ui.Screen.BACK_GESTURE
@@ -63,17 +62,16 @@
onActionKeyTutorialClicked = {},
onDoneButtonClicked = closeTutorial
)
- BACK_GESTURE -> BackGestureTutorialScreen()
+ BACK_GESTURE ->
+ BackGestureTutorialScreen(
+ onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
+ onBack = { vm.goTo(TUTORIAL_SELECTION) }
+ )
HOME_GESTURE -> HomeGestureTutorialScreen()
}
}
@Composable
-fun BackGestureTutorialScreen() {
- val vm = viewModel<BackGestureTutorialViewModel>(factory = GestureViewModelFactory())
-}
-
-@Composable
fun HomeGestureTutorialScreen() {
val vm = viewModel<HomeGestureTutorialViewModel>(factory = GestureViewModelFactory())
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt
new file mode 100644
index 0000000..16fa91d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialComponents.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.view
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.android.systemui.res.R
+
+@Composable
+fun DoneButton(onDoneButtonClicked: () -> Unit, modifier: Modifier = Modifier) {
+ Row(
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.fillMaxWidth()
+ ) {
+ Button(onClick = onDoneButtonClicked) {
+ Text(stringResource(R.string.touchpad_tutorial_done_button))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt
index 532eb1b..877bbe1 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TutorialSelectionScreen.kt
@@ -22,7 +22,6 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
@@ -114,16 +113,3 @@
Text(text = text, style = MaterialTheme.typography.headlineLarge)
}
}
-
-@Composable
-private fun DoneButton(onDoneButtonClicked: () -> Unit, modifier: Modifier = Modifier) {
- Row(
- horizontalArrangement = Arrangement.End,
- verticalAlignment = Alignment.CenterVertically,
- modifier = modifier.fillMaxWidth()
- ) {
- Button(onClick = onDoneButtonClicked) {
- Text(stringResource(R.string.touchpad_tutorial_done_button))
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 4238254..7fa165c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -73,6 +73,7 @@
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.argumentCaptor
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -218,6 +219,13 @@
verify(kosmos.windowManager).addView(any(), any())
+ var viewCaptor = argumentCaptor<View>()
+ verify(kosmos.windowManager).addView(viewCaptor.capture(), any())
+ verify(viewCaptor.firstValue)
+ .announceForAccessibility(
+ mContext.getText(R.string.accessibility_side_fingerprint_indicator_label)
+ )
+
// Hide alternate bouncer
kosmos.keyguardBouncerRepository.setAlternateVisible(false)
runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt
new file mode 100644
index 0000000..ee69c30
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.content.applicationContext
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceconfig.domain.interactor.deviceConfigInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigation.domain.interactor.navigationInteractor
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.statusBarStartable by Fixture {
+ StatusBarStartable(
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher,
+ applicationContext = applicationContext,
+ selectedUserInteractor = selectedUserInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ sceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor,
+ deviceConfigInteractor = deviceConfigInteractor,
+ navigationInteractor = navigationInteractor,
+ authenticationInteractor = authenticationInteractor,
+ powerInteractor = powerInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ statusBarService = statusBarService,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1f2ecb7..ed335f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,7 +39,7 @@
// User id to represent a non system (human) user id. We presume this is the main user.
const val MAIN_USER_ID = 10
- private const val DEFAULT_SELECTED_USER = 0
+ const val DEFAULT_SELECTED_USER = 0
private val DEFAULT_SELECTED_USER_INFO =
UserInfo(
/* id= */ DEFAULT_SELECTED_USER,
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 5567707..d7649dc 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
package com.android.server.appwidget;
import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
+import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -166,6 +167,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
import java.util.function.LongSupplier;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
@@ -457,7 +460,7 @@
break;
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
added = true;
- // Follow through
+ // fall through
case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
break;
@@ -3571,6 +3574,13 @@
}
out.endTag(null, "gs");
+
+ if (supportResumeRestoreAfterReboot()
+ && mBackupRestoreController.requiresPersistenceLocked()) {
+ AppWidgetXmlUtil.writeBackupRestoreControllerState(
+ out, mBackupRestoreController.getStateLocked(userId));
+ }
+
out.endDocument();
return true;
} catch (IOException e) {
@@ -3717,6 +3727,32 @@
LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget,
hostTag, providerTag);
outLoadedWidgets.add(loadedWidgets);
+ } else if (supportResumeRestoreAfterReboot()
+ && AppWidgetXmlUtil.TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)) {
+ final BackupRestoreController.State s =
+ AppWidgetXmlUtil.readBackupRestoreControllerState(parser);
+ if (s == null) {
+ continue;
+ }
+ final Set<String> prunedAppsInFile = s.getPrunedApps();
+ if (prunedAppsInFile != null) {
+ final Set<String> prunedAppsInMemory = mBackupRestoreController
+ .mPrunedAppsPerUser.get(userId);
+ if (prunedAppsInMemory == null) {
+ mBackupRestoreController.mPrunedAppsPerUser.put(
+ userId, prunedAppsInFile);
+ } else {
+ prunedAppsInMemory.addAll(prunedAppsInFile);
+ }
+ }
+ loadUpdateRecords(s.getUpdatesByProvider(),
+ this::findProviderByTag,
+ mBackupRestoreController.mUpdatesByProvider::get,
+ mBackupRestoreController.mUpdatesByProvider::put);
+ loadUpdateRecords(s.getUpdatesByHost(),
+ this::findHostByTag,
+ mBackupRestoreController.mUpdatesByHost::get,
+ mBackupRestoreController.mUpdatesByHost::put);
}
}
} while (type != XmlPullParser.END_DOCUMENT);
@@ -3732,6 +3768,36 @@
return version;
}
+ private <T> void loadUpdateRecords(
+ @Nullable final SparseArray<
+ List<BackupRestoreController.RestoreUpdateRecord>> updatesOnFile,
+ @NonNull final Function<Integer, T> findKeyByTagCb,
+ @NonNull final Function<T, List<
+ BackupRestoreController.RestoreUpdateRecord>> findRecordsCb,
+ @NonNull final BiConsumer<T, List<
+ BackupRestoreController.RestoreUpdateRecord>> newRecordsCb) {
+ if (updatesOnFile == null) {
+ return;
+ }
+ for (int i = 0; i < updatesOnFile.size(); i++) {
+ final int tag = updatesOnFile.keyAt(i);
+ final List<
+ BackupRestoreController.RestoreUpdateRecord
+ > recordsOnFile = updatesOnFile.get(tag);
+ if (recordsOnFile == null || recordsOnFile.isEmpty()) {
+ continue;
+ }
+ final T key = findKeyByTagCb.apply(tag);
+ final List<BackupRestoreController.RestoreUpdateRecord> recordsInMemory =
+ findRecordsCb.apply(key);
+ if (recordsInMemory != null) {
+ recordsInMemory.addAll(recordsOnFile);
+ } else {
+ newRecordsCb.accept(key, recordsOnFile);
+ }
+ }
+ }
+
private void performUpgradeLocked(int fromVersion) {
if (fromVersion < CURRENT_VERSION) {
Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to "
@@ -4674,7 +4740,7 @@
}
}
- private static final class Provider {
+ static final class Provider {
ProviderId id;
AppWidgetProviderInfo info;
@@ -4931,7 +4997,7 @@
}
}
- private static final class Host {
+ static final class Host {
HostId id;
ArrayList<Widget> widgets = new ArrayList<>();
IAppWidgetHost callbacks;
@@ -5250,10 +5316,10 @@
/**
* This class encapsulates the backup and restore logic for a user group state.
*/
- private final class BackupRestoreController {
+ final class BackupRestoreController {
private static final String TAG = "BackupRestoreController";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = AppWidgetServiceImpl.DEBUG;
// Version of backed-up widget state.
private static final int WIDGET_STATE_VERSION = 2;
@@ -5262,16 +5328,31 @@
// a given package. Keep track of what we've done so far here; the list is
// cleared at the start of every system restore pass, but preserved through
// any install-time restore operations.
+ @GuardedBy("AppWidgetServiceImpl.this.mLock")
private final SparseArray<Set<String>> mPrunedAppsPerUser = new SparseArray<>();
- private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider =
- new HashMap<>();
- private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost =
- new HashMap<>();
+ @GuardedBy("AppWidgetServiceImpl.this.mLock")
+ final Map<Provider, List<RestoreUpdateRecord>> mUpdatesByProvider =
+ new ArrayMap<>();
- @GuardedBy("mLock")
+ @GuardedBy("AppWidgetServiceImpl.this.mLock")
+ private final Map<Host, List<RestoreUpdateRecord>> mUpdatesByHost =
+ new ArrayMap<>();
+
+ @GuardedBy("AppWidgetServiceImpl.this.mLock")
private boolean mHasSystemRestoreFinished;
+ @GuardedBy("AppWidgetServiceImpl.this.mLock")
+ public boolean requiresPersistenceLocked() {
+ if (mHasSystemRestoreFinished) {
+ // No need to persist intermediate states if system restore is already finished.
+ return false;
+ }
+ // If either of the internal states is non-empty, then we need to persist that
+ return !(mPrunedAppsPerUser.size() == 0 && mUpdatesByProvider.isEmpty()
+ && mUpdatesByHost.isEmpty());
+ }
+
public List<String> getWidgetParticipants(int userId) {
if (DEBUG) {
Slog.i(TAG, "Getting widget participants for user: " + userId);
@@ -5436,7 +5517,7 @@
// If there's no live entry for this provider, add an inactive one
// so that widget IDs referring to them can be properly allocated
- // Backup and resotre only for the parent profile.
+ // Backup and restore only for the parent profile.
ComponentName componentName = new ComponentName(pkg, cl);
Provider p = findProviderLocked(componentName, userId);
@@ -5579,9 +5660,9 @@
final UserHandle userHandle = new UserHandle(userId);
// Build the providers' broadcasts and send them off
- Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
+ Set<Map.Entry<Provider, List<RestoreUpdateRecord>>> providerEntries
= mUpdatesByProvider.entrySet();
- for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
+ for (Map.Entry<Provider, List<RestoreUpdateRecord>> e : providerEntries) {
// For each provider there's a list of affected IDs
Provider provider = e.getKey();
if (provider.zombie) {
@@ -5589,7 +5670,7 @@
// We'll be called again when the provider is installed.
continue;
}
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ List<RestoreUpdateRecord> updates = e.getValue();
final int pending = countPendingUpdates(updates);
if (DEBUG) {
Slog.i(TAG, "Provider " + provider + " pending: " + pending);
@@ -5618,12 +5699,12 @@
}
// same thing per host
- Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
+ Set<Map.Entry<Host, List<RestoreUpdateRecord>>> hostEntries
= mUpdatesByHost.entrySet();
- for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
+ for (Map.Entry<Host, List<RestoreUpdateRecord>> e : hostEntries) {
Host host = e.getKey();
if (host.id.uid != UNKNOWN_UID) {
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ List<RestoreUpdateRecord> updates = e.getValue();
final int pending = countPendingUpdates(updates);
if (DEBUG) {
Slog.i(TAG, "Host " + host + " pending: " + pending);
@@ -5714,8 +5795,9 @@
return false;
}
+ @GuardedBy("mLock")
private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
+ List<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
if (r == null) {
r = new ArrayList<>();
mUpdatesByProvider.put(provider, r);
@@ -5732,7 +5814,7 @@
r.add(new RestoreUpdateRecord(oldId, newId));
}
- private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
+ private boolean alreadyStashed(List<RestoreUpdateRecord> stash,
final int oldId, final int newId) {
final int N = stash.size();
for (int i = 0; i < N; i++) {
@@ -5744,8 +5826,9 @@
return false;
}
+ @GuardedBy("mLock")
private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
+ List<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
if (r == null) {
r = new ArrayList<>();
mUpdatesByHost.put(host, r);
@@ -5835,7 +5918,7 @@
|| widget.provider.getUserId() == userId);
}
- private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
+ private int countPendingUpdates(List<RestoreUpdateRecord> updates) {
int pending = 0;
final int N = updates.size();
for (int i = 0; i < N; i++) {
@@ -5847,9 +5930,28 @@
return pending;
}
+ @GuardedBy("mLock")
+ @NonNull
+ private State getStateLocked(final int userId) {
+ final Set<String> prunedApps = mPrunedAppsPerUser.get(userId);
+ final SparseArray<List<RestoreUpdateRecord>> updatesByProvider = new SparseArray<>();
+ final SparseArray<List<RestoreUpdateRecord>> updatesByHost = new SparseArray<>();
+ mUpdatesByProvider.forEach((p, updates) -> {
+ if (p.getUserId() == userId) {
+ updatesByProvider.put(p.tag, new ArrayList<>(updates));
+ }
+ });
+ mUpdatesByHost.forEach((h, updates) -> {
+ if (h.getUserId() == userId) {
+ updatesByHost.put(h.tag, new ArrayList<>(updates));
+ }
+ });
+ return new State(prunedApps, updatesByProvider, updatesByHost);
+ }
+
// Accumulate a list of updates that affect the given provider for a final
// coalesced notification broadcast once restore is over.
- private class RestoreUpdateRecord {
+ static class RestoreUpdateRecord {
public int oldId;
public int newId;
public boolean notified;
@@ -5860,6 +5962,45 @@
notified = false;
}
}
+
+ static final class State {
+ // We need to make sure to wipe the pre-restore widget state only once for
+ // a given package. Keep track of what we've done so far here; the list is
+ // cleared at the start of every system restore pass, but preserved through
+ // any install-time restore operations.
+ @Nullable
+ private final Set<String> mPrunedApps;
+
+ @Nullable
+ private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByProvider;
+
+ @Nullable
+ private final SparseArray<List<RestoreUpdateRecord>> mUpdatesByHost;
+
+ State(
+ @Nullable final Set<String> prunedApps,
+ @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByProvider,
+ @Nullable final SparseArray<List<RestoreUpdateRecord>> updatesByHost) {
+ mPrunedApps = prunedApps;
+ mUpdatesByProvider = updatesByProvider;
+ mUpdatesByHost = updatesByHost;
+ }
+
+ @Nullable
+ Set<String> getPrunedApps() {
+ return mPrunedApps;
+ }
+
+ @Nullable
+ SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByProvider() {
+ return mUpdatesByProvider;
+ }
+
+ @Nullable
+ SparseArray<List<BackupRestoreController.RestoreUpdateRecord>> getUpdatesByHost() {
+ return mUpdatesByHost;
+ }
+ }
}
private class AppWidgetManagerLocal extends AppWidgetManagerInternal {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index d781cd8..ce9130a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -22,17 +22,24 @@
import android.content.ComponentName;
import android.os.Build;
import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
import android.util.SizeF;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -65,6 +72,16 @@
private static final String ATTR_DESCRIPTION_RES = "description_res";
private static final String ATTR_PROVIDER_INHERITANCE = "provider_inheritance";
private static final String ATTR_OS_FINGERPRINT = "os_fingerprint";
+ static final String TAG_BACKUP_RESTORE_CONTROLLER_STATE = "br";
+ private static final String TAG_PRUNED_APPS = "pruned_apps";
+ private static final String ATTR_TAG = "tag";
+ private static final String ATTR_PACKAGE_NAMES = "pkgs";
+ private static final String TAG_PROVIDER_UPDATES = "provider_updates";
+ private static final String TAG_HOST_UPDATES = "host_updates";
+ private static final String TAG_RECORD = "record";
+ private static final String ATTR_OLD_ID = "old_id";
+ private static final String ATTR_NEW_ID = "new_id";
+ private static final String ATTR_NOTIFIED = "notified";
private static final String SIZE_SEPARATOR = ",";
/**
@@ -165,4 +182,168 @@
return null;
}
}
+
+ /**
+ * Persists {@link AppWidgetServiceImpl.BackupRestoreController.State} to disk as XML.
+ * See {@link #readBackupRestoreControllerState(TypedXmlPullParser)} for example XML.
+ *
+ * @param out XML serializer
+ * @param state {@link AppWidgetServiceImpl.BackupRestoreController.State} of
+ * intermediate states to be persisted as xml to resume restore after reboot.
+ */
+ static void writeBackupRestoreControllerState(
+ @NonNull final TypedXmlSerializer out,
+ @NonNull final AppWidgetServiceImpl.BackupRestoreController.State state)
+ throws IOException {
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(state);
+ out.startTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE);
+ final Set<String> prunedApps = state.getPrunedApps();
+ if (prunedApps != null && !prunedApps.isEmpty()) {
+ out.startTag(null, TAG_PRUNED_APPS);
+ out.attribute(null, ATTR_PACKAGE_NAMES, String.join(",", prunedApps));
+ out.endTag(null, TAG_PRUNED_APPS);
+ }
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ updatesByProvider = state.getUpdatesByProvider();
+ if (updatesByProvider != null) {
+ writeUpdateRecords(out, TAG_PROVIDER_UPDATES, updatesByProvider);
+ }
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ updatesByHost = state.getUpdatesByHost();
+ if (updatesByHost != null) {
+ writeUpdateRecords(out, TAG_HOST_UPDATES, updatesByHost);
+ }
+ out.endTag(null, TAG_BACKUP_RESTORE_CONTROLLER_STATE);
+ }
+
+ private static void writeUpdateRecords(@NonNull final TypedXmlSerializer out,
+ @NonNull final String outerTag, @NonNull final SparseArray<List<
+ AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> records)
+ throws IOException {
+ for (int i = 0; i < records.size(); i++) {
+ final int tag = records.keyAt(i);
+ out.startTag(null, outerTag);
+ out.attributeInt(null, ATTR_TAG, tag);
+ final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> entries =
+ records.get(tag);
+ for (AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord entry : entries) {
+ out.startTag(null, TAG_RECORD);
+ out.attributeInt(null, ATTR_OLD_ID, entry.oldId);
+ out.attributeInt(null, ATTR_NEW_ID, entry.newId);
+ out.attributeBoolean(null, ATTR_NOTIFIED, entry.notified);
+ out.endTag(null, TAG_RECORD);
+ }
+ out.endTag(null, outerTag);
+ }
+ }
+
+ /**
+ * Parses {@link AppWidgetServiceImpl.BackupRestoreController.State} from xml.
+ *
+ * <pre>
+ * {@code
+ * <?xml version="1.0"?>
+ * <br>
+ * <pruned_apps pkgs="com.example.app1,com.example.app2,com.example.app3" />
+ * <provider_updates tag="0">
+ * <record old_id="10" new_id="0" notified="false" />
+ * </provider_updates>
+ * <provider_updates tag="1">
+ * <record old_id="9" new_id="1" notified="true" />
+ * </provider_updates>
+ * <provider_updates tag="2">
+ * <record old_id="8" new_id="2" notified="false" />
+ * </provider_updates>
+ * <host_updates tag="0">
+ * <record old_id="10" new_id="0" notified="false" />
+ * </host_updates>
+ * <host_updates tag="1">
+ * <record old_id="9" new_id="1" notified="true" />
+ * </host_updates>
+ * <host_updates tag="2">
+ * <record old_id="8" new_id="2" notified="false" />
+ * </host_updates>
+ * </br>
+ * }
+ * </pre>
+ *
+ * @param parser XML parser
+ * @return {@link AppWidgetServiceImpl.BackupRestoreController.State} of intermediate states
+ * in {@link AppWidgetServiceImpl.BackupRestoreController}, so that backup & restore can be
+ * resumed after reboot.
+ */
+ @Nullable
+ static AppWidgetServiceImpl.BackupRestoreController.State
+ readBackupRestoreControllerState(@NonNull final TypedXmlPullParser parser) {
+ Objects.requireNonNull(parser);
+ int type;
+ String tag = null;
+ final Set<String> prunedApps = new ArraySet<>(1);
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ updatesByProviders = new SparseArray<>();
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ updatesByHosts = new SparseArray<>();
+
+ try {
+ do {
+ type = parser.next();
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ tag = parser.getName();
+ switch (tag) {
+ case TAG_PRUNED_APPS:
+ final String packages =
+ parser.getAttributeValue(null, ATTR_PACKAGE_NAMES);
+ prunedApps.addAll(Arrays.asList(packages.split(",")));
+ break;
+ case TAG_PROVIDER_UPDATES:
+ updatesByProviders.put(parser.getAttributeInt(null, ATTR_TAG),
+ parseRestoreUpdateRecords(parser));
+ break;
+ case TAG_HOST_UPDATES:
+ updatesByHosts.put(parser.getAttributeInt(null, ATTR_TAG),
+ parseRestoreUpdateRecords(parser));
+ break;
+ default:
+ break;
+ }
+ } while (type != XmlPullParser.END_DOCUMENT
+ && (!TAG_BACKUP_RESTORE_CONTROLLER_STATE.equals(tag)
+ || type != XmlPullParser.END_TAG));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "error parsing state", e);
+ return null;
+ }
+ return new AppWidgetServiceImpl.BackupRestoreController.State(
+ prunedApps, updatesByProviders, updatesByHosts);
+ }
+
+ @NonNull
+ private static List<
+ AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord
+ > parseRestoreUpdateRecords(@NonNull final TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ String tag;
+ final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord> ret =
+ new ArrayList<>();
+ do {
+ type = parser.next();
+ tag = parser.getName();
+ if (tag.equals(TAG_RECORD) && type == XmlPullParser.START_TAG) {
+ final int oldId = parser.getAttributeInt(null, ATTR_OLD_ID);
+ final int newId = parser.getAttributeInt(null, ATTR_NEW_ID);
+ final boolean notified = parser.getAttributeBoolean(
+ null, ATTR_NOTIFIED);
+ final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record =
+ new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord(
+ oldId, newId);
+ record.notified = notified;
+ ret.add(record);
+ }
+ } while (tag.equals(TAG_RECORD));
+ return ret;
+ }
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 256f2b3..361b818 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -63,7 +63,7 @@
# NotificationManagerService.java
# ---------------------------
# when a NotificationManager.notify is called. status: 0=post, 1=update, 2=ignored
-2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(status|1)
+2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(status|1),(app_provided|1)
# when someone tries to cancel a notification, the notification manager sometimes
# calls this with flags too
2751 notification_cancel (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 022df9a..b4fd341 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16289,7 +16289,6 @@
String[] excludedPackages, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
-
synchronized(this) {
intent = verifyBroadcastLocked(intent);
@@ -16303,12 +16302,6 @@
// Permission regimes around sender-supplied broadcast options.
enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
- final ComponentName cn = intent.getComponent();
-
- Trace.traceBegin(
- Trace.TRACE_TAG_ACTIVITY_MANAGER,
- "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
final long origId = Binder.clearCallingIdentity();
try {
return broadcastIntentLocked(callerApp,
@@ -16319,7 +16312,6 @@
callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
} finally {
Binder.restoreCallingIdentity(origId);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0e81eb9..8e8a037 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -31,8 +31,8 @@
import android.app.UserSwitchObserver;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
-import android.content.ContentResolver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -233,6 +233,7 @@
private static final boolean DEFAULT_KEYGUARD_ENABLED = true;
private static final boolean DEFAULT_APP_ENABLED = true;
private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
+ private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false;
// Some devices that shipped before S already have face-specific settings. Instead of
// migrating, which is complicated, let's just keep using the existing settings.
@@ -253,6 +254,8 @@
Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED);
private final Uri BIOMETRIC_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
+ private final Uri MANDATORY_BIOMETRICS_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS);
private final ContentResolver mContentResolver;
private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
@@ -260,6 +263,7 @@
private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
+ private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>();
/**
* Creates a content observer.
@@ -281,6 +285,9 @@
mUseLegacyFaceOnlySettings =
Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
&& hasFace && !hasFingerprint;
+ mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser(
+ mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0);
updateContentObserver();
}
@@ -311,6 +318,10 @@
false /* notifyForDescendants */,
this /* observer */,
UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
}
@Override
@@ -353,6 +364,12 @@
Settings.Secure.BIOMETRIC_APP_ENABLED,
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
userId) != 0);
+ } else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
+ mMandatoryBiometricsEnabled.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */,
+ userId) != 0);
}
}
@@ -394,6 +411,11 @@
}
}
+ public boolean getMandatoryBiometricsEnabledForUser(int userId) {
+ return mMandatoryBiometricsEnabled.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS);
+ }
+
void notifyEnabledOnKeyguardCallbacks(int userId) {
List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
for (int i = 0; i < callbacks.size(); i++) {
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index f085647..b9e6563 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -29,11 +29,13 @@
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.R;
import com.android.server.biometrics.sensors.LockoutTracker;
import java.lang.annotation.Retention;
@@ -59,6 +61,7 @@
static final int BIOMETRIC_LOCKOUT_TIMED = 10;
static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
+ static final int MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR = 13;
private static final String TAG = "BiometricService/PreAuthInfo";
final boolean credentialRequested;
// Sensors that can be used for this request (e.g. strong enough, enrolled, enabled).
@@ -73,12 +76,14 @@
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
private final BiometricCameraManager mBiometricCameraManager;
+ private final boolean mOnlyMandatoryBiometricsRequested;
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context, BiometricCameraManager biometricCameraManager) {
+ PromptInfo promptInfo, int userId, Context context,
+ BiometricCameraManager biometricCameraManager,
+ boolean isOnlyMandatoryBiometricsRequested) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
mBiometricCameraManager = biometricCameraManager;
@@ -87,10 +92,11 @@
this.eligibleSensors = eligibleSensors;
this.ineligibleSensors = ineligibleSensors;
this.credentialAvailable = credentialAvailable;
- this.confirmationRequested = confirmationRequested;
- this.ignoreEnrollmentState = ignoreEnrollmentState;
+ this.confirmationRequested = promptInfo.isConfirmationRequested();
+ this.ignoreEnrollmentState = promptInfo.isIgnoreEnrollmentState();
this.userId = userId;
this.context = context;
+ this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested;
}
static PreAuthInfo create(ITrustManager trustManager,
@@ -102,7 +108,16 @@
BiometricCameraManager biometricCameraManager)
throws RemoteException {
- final boolean confirmationRequested = promptInfo.isConfirmationRequested();
+ final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators()
+ == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
+
+ if (dropCredentialFallback(promptInfo.getAuthenticators(),
+ settingObserver.getMandatoryBiometricsEnabledForUser(userId),
+ trustManager)) {
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
+ }
+
final boolean biometricRequested = Utils.isBiometricRequested(promptInfo);
final int requestedStrength = Utils.getPublicBiometricStrength(promptInfo);
final boolean credentialRequested = Utils.isCredentialRequested(promptInfo);
@@ -150,8 +165,27 @@
}
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
- eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
+ eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId,
+ context, biometricCameraManager, isOnlyMandatoryBiometricsRequested);
+ }
+
+ private static boolean dropCredentialFallback(int authenticators,
+ boolean isMandatoryBiometricsEnabled, ITrustManager trustManager) {
+ final boolean isMandatoryBiometricsRequested =
+ (authenticators & BiometricManager.Authenticators.MANDATORY_BIOMETRICS)
+ == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
+ if (Flags.mandatoryBiometrics() && isMandatoryBiometricsEnabled
+ && isMandatoryBiometricsRequested) {
+ try {
+ final boolean isInSignificantPlace = trustManager.isInSignificantPlace();
+ return !isInSignificantPlace;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception while trying to check "
+ + "if user is in a trusted location.");
+ }
+ }
+
+ return false;
}
/**
@@ -353,6 +387,25 @@
status = CREDENTIAL_NOT_ENROLLED;
}
}
+ } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) {
+ if (!eligibleSensors.isEmpty()) {
+ for (BiometricSensor sensor : eligibleSensors) {
+ modality |= sensor.modality;
+ }
+
+ if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face, credential is unavailable,
+ // and the face sensor privacy is enabled then return
+ // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will not be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
+ } else {
+ status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR;
+ }
} else if (mBiometricRequested) {
if (!eligibleSensors.isEmpty()) {
for (BiometricSensor sensor : eligibleSensors) {
@@ -509,7 +562,8 @@
CREDENTIAL_NOT_ENROLLED,
BIOMETRIC_LOCKOUT_TIMED,
BIOMETRIC_LOCKOUT_PERMANENT,
- BIOMETRIC_SENSOR_PRIVACY_ENABLED})
+ BIOMETRIC_SENSOR_PRIVACY_ENABLED,
+ MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorStatus {
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 4af30a9..df29ca4 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -140,6 +140,14 @@
}
/**
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return true if mandatory biometrics is requested
+ */
+ static boolean isMandatoryBiometricsRequested(@Authenticators.Types int authenticators) {
+ return (authenticators & Authenticators.MANDATORY_BIOMETRICS) != 0;
+ }
+
+ /**
* @param promptInfo should be first processed by
* {@link #combineAuthenticatorBundles(PromptInfo)}
* @return true if device credential is allowed.
@@ -242,7 +250,8 @@
// Check if any of the non-biometric and non-credential bits are set. If so, this is
// invalid.
final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
- | Authenticators.BIOMETRIC_MIN_STRENGTH);
+ | Authenticators.BIOMETRIC_MIN_STRENGTH
+ | Authenticators.MANDATORY_BIOMETRICS);
if ((authenticators & testBits) != 0) {
Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+ " Authenticators: " + authenticators);
@@ -259,6 +268,8 @@
return true;
} else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
return true;
+ } else if (isMandatoryBiometricsRequested(authenticators)) {
+ return true;
}
Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: "
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index efca598..c3ace15 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -19,13 +19,11 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.service.notification.Condition;
-import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
import android.util.Log;
@@ -41,9 +39,6 @@
private static final String TAG = "ConditionProviders.CCP";
private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
- public static final ComponentName COMPONENT =
- new ComponentName("android", CountdownConditionProvider.class.getName());
-
private static final String ACTION = CountdownConditionProvider.class.getName();
private static final int REQUEST_CODE = 100;
private static final String EXTRA_CONDITION_ID = "condition_id";
@@ -60,31 +55,16 @@
}
@Override
- public ComponentName getComponent() {
- return COMPONENT;
- }
-
- @Override
public boolean isValidConditionId(Uri id) {
return ZenModeConfig.isValidCountdownConditionId(id);
}
@Override
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- @Override
public void onBootComplete() {
// noop
}
@Override
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index 4fe7a27..00dd547 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -19,7 +19,6 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -31,7 +30,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.Condition;
-import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
import android.util.ArraySet;
@@ -54,8 +52,6 @@
private static final String TAG = "ConditionProviders.ECP";
private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
- public static final ComponentName COMPONENT =
- new ComponentName("android", EventConditionProvider.class.getName());
private static final String NOT_SHOWN = "...";
private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
@@ -82,11 +78,6 @@
}
@Override
- public ComponentName getComponent() {
- return COMPONENT;
- }
-
- @Override
public boolean isValidConditionId(Uri id) {
return ZenModeConfig.isValidEventConditionId(id);
}
@@ -166,16 +157,6 @@
}
}
- @Override
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- @Override
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
private void reloadTrackers() {
if (DEBUG) Slog.d(TAG, "reloadTrackers");
for (int i = 0; i < mTrackers.size(); i++) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b436c8b..f297708 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -46,6 +46,10 @@
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED;
@@ -98,6 +102,11 @@
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
@@ -458,7 +467,8 @@
Adjustment.KEY_IMPORTANCE_PROPOSAL,
Adjustment.KEY_SENSITIVE_CONTENT,
Adjustment.KEY_RANKING_SCORE,
- Adjustment.KEY_NOT_CONVERSATION
+ Adjustment.KEY_NOT_CONVERSATION,
+ Adjustment.KEY_TYPE
};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
@@ -1023,7 +1033,7 @@
summary.getSbn().getNotification().color = summaryAttr.iconColor;
summary.getSbn().getNotification().visibility = summaryAttr.visibility;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
- mPostNotificationTrackerFactory.newTracker(null)));
+ /* isAppProvided= */ false, mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1650,7 +1660,7 @@
// Force isAppForeground true here, because for sysui's purposes we
// want to adjust the flag behaviour.
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
- r, true /* isAppForeground*/,
+ r, /* isAppForeground= */ true , /* isAppProvided= */ false,
mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1681,7 +1691,7 @@
// want to be able to adjust the flag behaviour.
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
- /* foreground= */ true,
+ /* foreground= */ true, /* isAppProvided= */ false,
mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -2706,7 +2716,7 @@
enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn,
- false /* byForegroundService */);
+ /* byForegroundService= */ false, /* isAppProvided= */ false);
} catch (Exception e) {
Slog.e(TAG, "Cannot un-snooze notification", e);
}
@@ -2851,6 +2861,7 @@
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+ /* isAppProvided= */ false,
mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -3763,7 +3774,7 @@
Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, userId,
- false /* byForegroundService */);
+ /* byForegroundService= */ false, /* isAppProvided= */ true);
}
@Override
@@ -6608,6 +6619,30 @@
for (String removeKey : toRemove) {
adjustments.remove(removeKey);
}
+ if (android.service.notification.Flags.notificationClassification()
+ && adjustments.containsKey(KEY_TYPE)) {
+ NotificationChannel newChannel = null;
+ int type = adjustments.getInt(KEY_TYPE);
+ if (TYPE_NEWS == type) {
+ newChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
+ } else if (TYPE_PROMOTION == type) {
+ newChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
+ } else if (TYPE_SOCIAL_MEDIA == type) {
+ newChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
+ } else if (TYPE_CONTENT_RECOMMENDATION == type) {
+ newChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+ }
+ if (newChannel == null) {
+ adjustments.remove(KEY_TYPE);
+ } else {
+ // swap app provided type with the real thing
+ adjustments.putParcelable(KEY_TYPE, newChannel);
+ }
+ }
r.addAdjustment(adjustment);
if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
logSensitiveAdjustmentReceived(isPosted,
@@ -7025,7 +7060,7 @@
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
- userId, false /* byForegroundService */);
+ userId, /* byForegroundService= */ false , /* isAppProvided= */ true);
}
@Override
@@ -7033,7 +7068,7 @@
String tag, int id, Notification notification, int userId,
boolean byForegroundService) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
- userId, byForegroundService);
+ userId, byForegroundService, /* isAppProvided= */ true);
}
@Override
@@ -7245,7 +7280,8 @@
r.getSbn().getInitialPid(), r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(),
r.getSbn().getUserId(), /* postSilently= */ true,
- /* byForegroundService= */ false);
+ /* byForegroundService= */ false,
+ /* isAppProvided= */ false);
}
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -7306,19 +7342,21 @@
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
- int incomingUserId, boolean byForegroundService) {
+ int incomingUserId, boolean byForegroundService, boolean isAppProvided) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
- incomingUserId, false /* postSilently */, byForegroundService);
+ incomingUserId, false /* postSilently */, byForegroundService, isAppProvided);
}
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
- int incomingUserId, boolean postSilently, boolean byForegroundService) {
+ int incomingUserId, boolean postSilently, boolean byForegroundService,
+ boolean isAppProvided) {
PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
boolean enqueued = false;
try {
enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
- notification, incomingUserId, postSilently, tracker, byForegroundService);
+ notification, incomingUserId, postSilently, tracker, byForegroundService,
+ isAppProvided);
} finally {
if (!enqueued) {
tracker.cancel();
@@ -7344,7 +7382,7 @@
private boolean enqueueNotificationInternal(final String pkg, final String opPkg, //HUI
final int callingUid, final int callingPid, final String tag, final int id,
final Notification notification, int incomingUserId, boolean postSilently,
- PostNotificationTracker tracker, boolean byForegroundService) {
+ PostNotificationTracker tracker, boolean byForegroundService, boolean isAppProvided) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
@@ -7545,7 +7583,8 @@
// Need escalated privileges to get package importance.
final int packageImportance = getPackageImportanceWithIdentity(pkg);
boolean isAppForeground = packageImportance == IMPORTANCE_FOREGROUND;
- mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, tracker));
+ mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+ /* isAppProvided= */ isAppProvided, tracker));
return true;
}
@@ -7925,6 +7964,7 @@
mHandler.post(
new EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
+ /* isAppProvided= */ false,
mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -8495,13 +8535,15 @@
private final NotificationRecord r;
private final int userId;
private final boolean isAppForeground;
+ private final boolean isAppProvided;
private final PostNotificationTracker mTracker;
EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground,
- PostNotificationTracker tracker) {
+ boolean isAppProvided, PostNotificationTracker tracker) {
this.userId = userId;
this.r = r;
this.isAppForeground = foreground;
+ this.isAppProvided = isAppProvided;
this.mTracker = checkNotNull(tracker);
}
@@ -8586,9 +8628,10 @@
if (old != null) {
enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
}
+ int appProvided = isAppProvided ? 1 : 0;
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString(),
- enqueueStatus);
+ enqueueStatus, appProvided);
}
// tell the assistant service about the notification
@@ -8748,7 +8791,7 @@
// was not autogrouped onPost, to avoid an unnecessary sort.
// We add the autogroup key to the notification without a
// sort here, and it'll be sorted below with extractSignals.
- addAutogroupKeyLocked(key, /*requestSort=*/false);
+ addAutogroupKeyLocked(key, /* requestSort= */false);
}
}
}
@@ -11373,7 +11416,7 @@
record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
- record, isAppForeground,
+ record, isAppForeground, /* isAppProvided= */ false,
mPostNotificationTrackerFactory.newTracker(null)));
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0c6a6c8..0d4bdf6 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -710,7 +710,8 @@
if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(
- Adjustment.KEY_SNOOZE_CRITERIA, android.service.notification.SnoozeCriterion.class);
+ Adjustment.KEY_SNOOZE_CRITERIA,
+ android.service.notification.SnoozeCriterion.class);
setSnoozeCriteria(snoozeCriterionList);
EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
snoozeCriterionList.toString());
@@ -736,7 +737,8 @@
}
if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
setSystemGeneratedSmartActions(
- signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, android.app.Notification.Action.class));
+ signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS,
+ android.app.Notification.Action.class));
EventLogTags.writeNotificationAdjusted(getKey(),
Adjustment.KEY_CONTEXTUAL_ACTIONS,
getSystemGeneratedSmartActions().toString());
@@ -778,6 +780,14 @@
Adjustment.KEY_SENSITIVE_CONTENT,
Boolean.toString(mSensitiveContent));
}
+ if (android.service.notification.Flags.notificationClassification()
+ && signals.containsKey(Adjustment.KEY_TYPE)) {
+ updateNotificationChannel(signals.getParcelable(Adjustment.KEY_TYPE,
+ NotificationChannel.class));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_TYPE,
+ mChannel.getId());
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 309e945..9ee05d8 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -22,11 +22,14 @@
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Flags.notificationClassification;
+
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -514,6 +517,10 @@
Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
}
+ if (notificationClassification()) {
+ addReservedChannelsLocked(r);
+ }
+
if (r.uid == UNKNOWN_UID) {
if (Flags.persistIncompleteRestoreData()) {
r.userId = userId;
@@ -603,6 +610,40 @@
return true;
}
+ private void addReservedChannelsLocked(PackagePreferences p) {
+ if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) {
+ NotificationChannel channel = new NotificationChannel(
+ NotificationChannel.PROMOTIONS_ID,
+ mContext.getString(R.string.promotional_notification_channel_label),
+ IMPORTANCE_LOW);
+ p.channels.put(channel.getId(), channel);
+ }
+
+ if (!p.channels.containsKey(NotificationChannel.RECS_ID)) {
+ NotificationChannel channel = new NotificationChannel(
+ NotificationChannel.RECS_ID,
+ mContext.getString(R.string.recs_notification_channel_label),
+ IMPORTANCE_LOW);
+ p.channels.put(channel.getId(), channel);
+ }
+
+ if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) {
+ NotificationChannel channel = new NotificationChannel(
+ NotificationChannel.NEWS_ID,
+ mContext.getString(R.string.news_notification_channel_label),
+ IMPORTANCE_LOW);
+ p.channels.put(channel.getId(), channel);
+ }
+
+ if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) {
+ NotificationChannel channel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID,
+ mContext.getString(R.string.social_notification_channel_label),
+ IMPORTANCE_LOW);
+ p.channels.put(channel.getId(), channel);
+ }
+ }
+
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
out.startTag(null, TAG_RANKING);
out.attributeInt(null, ATT_VERSION, XML_VERSION);
@@ -1801,7 +1842,7 @@
public boolean onlyHasDefaultChannel(String pkg, int uid) {
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
- if (r.channels.size() == 1
+ if (r.channels.size() == (notificationClassification() ? 5 : 1)
&& r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
return true;
}
@@ -2611,6 +2652,7 @@
context.getResources().getString(
R.string.default_notification_channel_label));
}
+ // TODO (b/346396459): Localize all reserved channels
}
}
}
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 737353d..6efe88f 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -20,7 +20,6 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -28,7 +27,6 @@
import android.os.Binder;
import android.provider.Settings;
import android.service.notification.Condition;
-import android.service.notification.IConditionProvider;
import android.service.notification.ScheduleCalendar;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
@@ -54,8 +52,6 @@
static final String TAG = "ConditionProviders.SCP";
static final boolean DEBUG = true || Log.isLoggable("ConditionProviders", Log.DEBUG);
- public static final ComponentName COMPONENT =
- new ComponentName("android", ScheduleConditionProvider.class.getName());
private static final String NOT_SHOWN = "...";
private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName();
private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
@@ -66,7 +62,8 @@
private final Context mContext = this;
private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
- private ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>();
+ @GuardedBy("mSnoozedForAlarm")
+ private final ArraySet<Uri> mSnoozedForAlarm = new ArraySet<>();
private AlarmManager mAlarmManager;
private boolean mConnected;
@@ -78,11 +75,6 @@
}
@Override
- public ComponentName getComponent() {
- return COMPONENT;
- }
-
- @Override
public boolean isValidConditionId(Uri id) {
return ZenModeConfig.isValidScheduleConditionId(id);
}
@@ -103,7 +95,10 @@
pw.println(mSubscriptions.get(conditionId).toString());
}
}
- pw.println(" snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm));
+ synchronized (mSnoozedForAlarm) {
+ pw.println(
+ " snoozed due to alarm: " + TextUtils.join(SEPARATOR, mSnoozedForAlarm));
+ }
dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
}
@@ -149,16 +144,6 @@
evaluateSubscriptions();
}
- @Override
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- @Override
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
private void evaluateSubscriptions() {
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -299,6 +284,7 @@
}
}
+ @GuardedBy("mSnoozedForAlarm")
private void saveSnoozedLocked() {
final String setting = TextUtils.join(SEPARATOR, mSnoozedForAlarm);
final int currentUser = ActivityManager.getCurrentUser();
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
index 574f04c..97073b7 100644
--- a/services/core/java/com/android/server/notification/SystemConditionProviderService.java
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -31,12 +31,21 @@
public abstract class SystemConditionProviderService extends ConditionProviderService {
abstract public void dump(PrintWriter pw, DumpFilter filter);
- abstract public void attachBase(Context context);
- abstract public IConditionProvider asInterface();
- abstract public ComponentName getComponent();
abstract public boolean isValidConditionId(Uri id);
abstract public void onBootComplete();
+ final ComponentName getComponent() {
+ return new ComponentName("android", this.getClass().getName());
+ }
+
+ final IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ final void attachBase(Context context) {
+ attachBaseContext(context);
+ }
+
protected static String ts(long time) {
return new Date(time) + " (" + time + ")";
}
diff --git a/services/core/java/com/android/server/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS
index 485a0dd..dfa07d8 100644
--- a/services/core/java/com/android/server/timezonedetector/OWNERS
+++ b/services/core/java/com/android/server/timezonedetector/OWNERS
@@ -2,7 +2,7 @@
# This is the main list for platform time / time zone detection maintainers, for this dir and
# ultimately referenced by other OWNERS files for components maintained by the same team.
nfuller@google.com
+boullanger@google.com
jmorace@google.com
kanyinsola@google.com
-mingaleev@google.com
-narayan@google.com
+mingaleev@google.com
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 8a7815e..c34e28f 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -47,7 +47,9 @@
import android.os.Handler;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
+import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.SparseArray;
import android.util.Xml;
import android.widget.RemoteViews;
@@ -67,10 +69,12 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
@@ -388,6 +392,93 @@
assertThat(target.previewLayout).isEqualTo(original.previewLayout);
}
+ public void testBackupRestoreControllerStatePersistence() throws IOException {
+ // Setup mock data
+ final Set<String> mockPrunedApps = getMockPrunedApps();
+ final SparseArray<
+ List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>
+ > mockUpdatesByProvider = getMockUpdates();
+ final SparseArray<
+ List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>
+ > mockUpdatesByHost = getMockUpdates();
+ final AppWidgetServiceImpl.BackupRestoreController.State state =
+ new AppWidgetServiceImpl.BackupRestoreController.State(
+ mockPrunedApps, mockUpdatesByProvider, mockUpdatesByHost);
+
+ final File file = new File(mTestContext.getDataDir(), "state.xml");
+ saveBackupRestoreControllerState(file, state);
+ final AppWidgetServiceImpl.BackupRestoreController.State target =
+ loadStateLocked(file);
+ assertNotNull(target);
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ actualUpdatesByProvider = target.getUpdatesByProvider();
+ assertNotNull(actualUpdatesByProvider);
+ final SparseArray<List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>>
+ actualUpdatesByHost = target.getUpdatesByHost();
+ assertNotNull(actualUpdatesByHost);
+
+ assertEquals(mockPrunedApps, target.getPrunedApps());
+ for (int i = 0; i < mockUpdatesByProvider.size(); i++) {
+ final int key = mockUpdatesByProvider.keyAt(i);
+ verifyRestoreUpdateRecord(
+ actualUpdatesByProvider.get(key), mockUpdatesByProvider.get(key));
+ }
+ for (int i = 0; i < mockUpdatesByHost.size(); i++) {
+ final int key = mockUpdatesByHost.keyAt(i);
+ verifyRestoreUpdateRecord(
+ actualUpdatesByHost.get(key), mockUpdatesByHost.get(key));
+ }
+ }
+
+ private void verifyRestoreUpdateRecord(
+ @NonNull final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>
+ actualUpdates,
+ @NonNull final List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>
+ expectedUpdates) {
+ assertEquals(expectedUpdates.size(), actualUpdates.size());
+ for (int i = 0; i < expectedUpdates.size(); i++) {
+ final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord expected =
+ expectedUpdates.get(i);
+ final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord actual =
+ actualUpdates.get(i);
+ assertEquals(expected.oldId, actual.oldId);
+ assertEquals(expected.newId, actual.newId);
+ assertEquals(expected.notified, actual.notified);
+ }
+ }
+
+ @NonNull
+ private static Set<String> getMockPrunedApps() {
+ final Set<String> mockPrunedApps = new ArraySet<>(10);
+ for (int i = 0; i < 10; i++) {
+ mockPrunedApps.add("com.example.app" + i);
+ }
+ return mockPrunedApps;
+ }
+
+ @NonNull
+ private static SparseArray<
+ List<AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>
+ > getMockUpdates() {
+ final SparseArray<List<
+ AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord>> ret =
+ new SparseArray<>(4);
+ ret.put(0, new ArrayList<>());
+ for (int i = 0; i < 5; i++) {
+ final AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord record =
+ new AppWidgetServiceImpl.BackupRestoreController.RestoreUpdateRecord(
+ 5 - i, i);
+ record.notified = (i % 2 == 1);
+ final int key = (i < 3) ? 1 : 2;
+ if (!ret.contains(key)) {
+ ret.put(key, new ArrayList<>());
+ }
+ ret.get(key).add(record);
+ }
+ ret.put(3, new ArrayList<>());
+ return ret;
+ }
+
private int setupHostAndWidget() {
List<PendingHostUpdate> updates = mService.startListening(
mMockHost, mPkgName, HOST_ID, new int[0]).getList();
@@ -418,6 +509,40 @@
return mTestContext.getResources().getInteger(resId);
}
+ private static void saveBackupRestoreControllerState(
+ @NonNull final File dst,
+ @Nullable final AppWidgetServiceImpl.BackupRestoreController.State state)
+ throws IOException {
+ Objects.requireNonNull(dst);
+ if (state == null) {
+ return;
+ }
+ final AtomicFile file = new AtomicFile(dst);
+ final FileOutputStream stream = file.startWrite();
+ final TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ AppWidgetXmlUtil.writeBackupRestoreControllerState(out, state);
+ out.endDocument();
+ file.finishWrite(stream);
+ }
+
+ private static AppWidgetServiceImpl.BackupRestoreController.State loadStateLocked(
+ @NonNull final File dst) {
+ Objects.requireNonNull(dst);
+ final AtomicFile file = new AtomicFile(dst);
+ try (FileInputStream stream = file.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // drain whitespace, comments, etc.
+ }
+ return AppWidgetXmlUtil.readBackupRestoreControllerState(parser);
+ } catch (IOException | XmlPullParserException e) {
+ return null;
+ }
+ }
+
private static void saveWidgetProviderInfoLocked(@NonNull final File dst,
@Nullable final AppWidgetProviderInfo info)
throws IOException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 503ab8e..0f38532 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -79,6 +79,9 @@
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.security.GateKeeper;
import android.security.KeyStoreAuthorization;
@@ -118,6 +121,8 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String TEST_PACKAGE_NAME = "test_package";
private static final long TEST_REQUEST_ID = 44;
@@ -1506,6 +1511,75 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testCanAuthenticate_whenMandatoryBiometricsRequested()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
+ mBiometricService.onStart();
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ .thenReturn(true);
+
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testCanAuthenticate_whenMandatoryBiometricsAndStrongAuthenticatorsRequested()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
+ mBiometricService.onStart();
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ .thenReturn(true);
+
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ | Authenticators.BIOMETRIC_STRONG));
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ | Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testCanAuthenticate_whenMandatoryBiometricsRequestedAndDeviceCredentialAvailable()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
+ mBiometricService.onStart();
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt()))
+ .thenReturn(true);
+
+ setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
+
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
+
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS
+ | Authenticators.DEVICE_CREDENTIAL));
+ }
+
+ @Test
public void testAuthenticatorActualStrength() {
// Tuple of OEM config, updatedStrength, and expectedStrength
final int[][] testCases = {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index b40d7ee..b831ef5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -32,11 +32,16 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.filters.SmallTest;
@@ -54,6 +59,9 @@
public class PreAuthInfoTest {
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
@@ -66,6 +74,8 @@
@Mock
Context mContext;
@Mock
+ Resources mResources;
+ @Mock
ITrustManager mTrustManager;
@Mock
DevicePolicyManager mDevicePolicyManager;
@@ -80,6 +90,7 @@
when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -91,6 +102,8 @@
.thenReturn(LOCKOUT_NONE);
when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getString(anyInt())).thenReturn(TEST_PACKAGE_NAME);
}
@Test
@@ -184,6 +197,54 @@
assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testMandatoryBiometricsStatus_whenAllRequirementsSatisfiedAndSensorAvailable()
+ throws Exception {
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testMandatoryBiometricsStatus_whenAllRequirementsSatisfiedAndSensorUnavailable()
+ throws Exception {
+ when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable()
+ throws Exception {
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS
+ | BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index b05a819..cb75e1a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -179,7 +179,8 @@
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
final int authenticator = 1 << i;
- if (authenticator == Authenticators.DEVICE_CREDENTIAL) {
+ if (authenticator == Authenticators.DEVICE_CREDENTIAL
+ || authenticator == Authenticators.MANDATORY_BIOMETRICS) {
continue;
}
assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java
index ddb9b43..58a1d9c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java
@@ -16,12 +16,15 @@
package com.android.server.notification;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.Application;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.testing.AndroidTestingRunner;
@@ -66,6 +69,12 @@
}
@Test
+ public void getComponent_returnsComponent() {
+ assertThat(mService.getComponent()).isEqualTo(new ComponentName("android",
+ "com.android.server.notification.CountdownConditionProvider"));
+ }
+
+ @Test
public void testGetPendingIntent() {
PendingIntent pi = mService.getPendingIntent(Uri.EMPTY);
assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
index 4c440ca..4af96ef 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
@@ -16,14 +16,16 @@
package com.android.server.notification;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.Application;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
-import android.net.Uri;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -63,7 +65,13 @@
service.onCreate();
service.onBind(startIntent);
mService = spy(service);
- }
+ }
+
+ @Test
+ public void getComponent_returnsComponent() {
+ assertThat(mService.getComponent()).isEqualTo(new ComponentName("android",
+ "com.android.server.notification.EventConditionProvider"));
+ }
@Test
public void testGetPendingIntent() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 398dc281..c1d7afb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -40,6 +40,7 @@
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -91,7 +92,9 @@
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
+import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Condition.SOURCE_CONTEXT;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_TRUE;
@@ -15778,4 +15781,27 @@
when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
}
+
+ @Test
+ @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testApplyAdjustment_keyType_validType() throws Exception {
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(r);
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+
+ Bundle signals = new Bundle();
+ signals.putInt(KEY_TYPE, TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(
+ r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+
+ waitForIdle();
+
+ r.applyAdjustments();
+
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index d1880d2..ff4bc21 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -23,6 +23,10 @@
import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationChannel.USER_LOCKED_LIGHTS;
@@ -47,6 +51,9 @@
import static android.os.UserHandle.USER_SYSTEM;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.notificationClassification;
+
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
@@ -184,6 +191,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
+@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
public class PreferencesHelperTest extends UiServiceTestCase {
private static final int UID_HEADLESS = 1000000;
private static final UserHandle USER = UserHandle.of(0);
@@ -233,7 +241,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
- FLAG_PERSIST_INCOMPLETE_RESTORE_DATA);
+ FLAG_NOTIFICATION_CLASSIFICATION);
}
public PreferencesHelperTest(FlagsParameterization flags) {
@@ -582,6 +590,16 @@
assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false,
UID_N_MR1, false));
+ NotificationChannel updateNews = null;
+ if (notificationClassification()) {
+ // change one of the reserved bundle channels to ensure changes are persisted across
+ // boot
+ updateNews = mHelper.getNotificationChannel(
+ PKG_N_MR1, UID_N_MR1, NEWS_ID, false).copy();
+ updateNews.setImportance(IMPORTANCE_NONE);
+ mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews, true, 1000, true);
+ }
+
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
@@ -597,6 +615,12 @@
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));
+ if (notificationClassification()) {
+ assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(),
+ false)).isNotNull();
+ assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(),
+ false)).isEqualTo(updateNews);
+ }
List<NotificationChannelGroup> actualGroups = mXmlHelper.getNotificationChannelGroups(
PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList();
@@ -1130,9 +1154,22 @@
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\" uid=\"10002\">\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
+ "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1140,6 +1177,10 @@
+ "=\"false\" uid=\"10001\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1149,14 +1190,22 @@
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ "</package>\n"
+ "<package name=\"com.example.p\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
- + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\" />\n"
- + "</ranking>";
+ + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\"";
assertThat(baos.toString()).contains(expected);
}
@@ -1216,9 +1265,22 @@
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance default because on in permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
@@ -1227,6 +1289,10 @@
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1236,6 +1302,15 @@
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1243,12 +1318,11 @@
// Importance default because on in permission helper
+ "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
- + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n"
- // Packages that exist solely in permissionhelper
- + "<package name=\"first\" importance=\"3\" />\n"
- + "<package name=\"third\" importance=\"0\" />\n"
- + "</ranking>";
+ + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\"";
assertThat(baos.toString()).contains(expected);
+ // Packages that exist solely in permissionhelper
+ assertThat(baos.toString()).contains("<package name=\"first\" importance=\"3\"");
+ assertThat(baos.toString()).contains("<package name=\"third\" importance=\"0\"");
}
@Test
@@ -1303,9 +1377,22 @@
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
+ "=\"false\">\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id\" name=\"name\" importance=\"2\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "</package>\n"
// Importance 0 because missing from permission helper
+ "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" "
@@ -1314,6 +1401,10 @@
+ "=\"false\">\n"
+ "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
+ "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.social\" "
+ + "name=\"Social\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1323,6 +1414,15 @@
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
+ "orig_imp=\"4\" />\n"
+ + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+ + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+ + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+ + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
+ "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+ "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1330,8 +1430,7 @@
// Importance default because on in permission helper
+ "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
- + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n"
- + "</ranking>";
+ + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\"";
assertThat(baos.toString()).contains(expected);
}
@@ -1851,8 +1950,8 @@
parser.nextTag();
mHelper.readXml(parser, false, UserHandle.USER_ALL);
- final NotificationChannel updated1 =
- mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated1 = mHelper.getNotificationChannel(
+ PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
assertTrue(updated1.canBypassDnd());
assertEquals(VISIBILITY_SECRET, updated1.getLockscreenVisibility());
@@ -2392,18 +2491,20 @@
// Returns only non-deleted channels
List<NotificationChannel> channels =
mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList();
- assertEquals(2, channels.size()); // Default channel + non-deleted channel
+ // Default channel + non-deleted channel + system defaults
+ assertEquals(notificationClassification() ? 6 : 2, channels.size());
for (NotificationChannel nc : channels) {
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ if (channel2.getId().equals(nc.getId())) {
compareChannels(channel2, nc);
}
}
// Returns deleted channels too
channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList();
- assertEquals(3, channels.size()); // Includes default channel
+ // Includes system channel(s)
+ assertEquals(notificationClassification() ? 7 : 3, channels.size());
for (NotificationChannel nc : channels) {
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ if (channel2.getId().equals(nc.getId())) {
compareChannels(channelMap.get(nc.getId()), nc);
}
}
@@ -3013,7 +3114,8 @@
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt()))
+ .thenReturn(legacy);
// create records with the default channel for all user 0 and user 1 uids
mHelper.canShowBadge(PKG_N_MR1, user0Uids[i]);
@@ -3024,13 +3126,14 @@
// user 0 records remain
for (int i = 0; i < user0Uids.length; i++) {
- assertEquals(1,
- mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false).getList().size());
+ assertEquals(notificationClassification() ? 5 : 1,
+ mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false)
+ .getList().size());
}
// user 1 records are gone
for (int i = 0; i < user1Uids.length; i++) {
- assertEquals(0,
- mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false)
+ .getList().size());
}
}
@@ -3054,7 +3157,8 @@
assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
- assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+ assertEquals(notificationClassification() ? 6 : 2,
+ mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
}
@Test
@@ -3126,7 +3230,8 @@
@Test
public void testRecordDefaults() throws Exception {
assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
- assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+ assertEquals(notificationClassification() ? 5 : 1,
+ mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
}
@Test
@@ -3212,7 +3317,9 @@
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
- assertEquals(2, group.getChannels().size()); // misc channel too
+ assertEquals(
+ notificationClassification() ? 6 : 2,
+ group.getChannels().size()); // misc channel too
assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
|| channel3.getId().equals(group.getChannels().get(1).getId()));
} else if (group.getId().equals(ncg.getId())) {
@@ -3363,6 +3470,9 @@
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false,
UID_N_MR1, false);
}
+ if (notificationClassification()) {
+ numChannels += 4;
+ }
expectedChannels.put(pkgName, numChannels);
}
@@ -4583,7 +4693,12 @@
@Test
public void testTooManyChannels() {
- for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
+ int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
+ if (notificationClassification()) {
+ // reserved channels lower limit
+ numToCreate -= 4;
+ }
+ for (int i = 0; i < numToCreate; i++) {
NotificationChannel channel = new NotificationChannel(String.valueOf(i),
String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false);
@@ -4602,11 +4717,16 @@
@Test
public void testTooManyChannels_xml() throws Exception {
+ int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
+ if (notificationClassification()) {
+ // reserved channels lower limit
+ numToCreate -= 4;
+ }
String extraChannel = "EXTRA";
String extraChannel1 = "EXTRA1";
// create first... many... directly so we don't need a big xml blob in this test
- for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
+ for (int i = 0; i < numToCreate; i++) {
NotificationChannel channel = new NotificationChannel(String.valueOf(i),
String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false);
@@ -5619,8 +5739,9 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- // number of channels with preferences should be 3 total
- assertEquals("expected number of events", 3, events.size());
+ assertEquals("expected number of events",
+ notificationClassification() ? 7 : 3,
+ events.size());
for (StatsEvent ev : events) {
// all of these events should be of PackageNotificationChannelPreferences type,
// and therefore we expect the atom to have this field.
@@ -5643,7 +5764,7 @@
} else if (eventChannelId.equals("a")) {
assertEquals("channel name", "a", p.getChannelName());
assertEquals("importance", IMPORTANCE_LOW, p.getImportance());
- } else { // b
+ } else if (eventChannelId.equals("b")){ // b
assertEquals("channel name", "b", p.getChannelName());
assertEquals("importance", IMPORTANCE_HIGH, p.getImportance());
}
@@ -5661,11 +5782,17 @@
mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false);
List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c"));
+ if (notificationClassification()) {
+ channels.add(NEWS_ID);
+ channels.add(PROMOTIONS_ID);
+ channels.add(SOCIAL_MEDIA_ID);
+ channels.add(RECS_ID);
+ }
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events", 3, events.size());
+ assertEquals("total events", notificationClassification() ? 7 : 3, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5699,7 +5826,7 @@
mHelper.pullPackageChannelPreferencesStats(events);
// In this case, we want to check the properties of the conversation channel (not parent)
- assertEquals("total events", 2, events.size());
+ assertEquals("total events", notificationClassification() ? 6 : 2, events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5731,7 +5858,9 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events", 2, events.size());
+ assertEquals("total events",
+ notificationClassification() ? 6 : 2,
+ events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5762,7 +5891,9 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- assertEquals("total events", 2, events.size());
+ assertEquals("total events",
+ notificationClassification() ? 6 : 2,
+ events.size());
for (StatsEvent ev : events) {
AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6044,6 +6175,23 @@
assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count());
}
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+ public void testNotificationBundles() {
+ // do something that triggers settings creation for an app
+ mHelper.setShowBadge(PKG_O, UID_O, true);
+
+ // verify 4 reserved channels are created
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
+ .isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
+ .getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false)
+ .getImportance()).isEqualTo(IMPORTANCE_LOW);
+ assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
+ .isEqualTo(IMPORTANCE_LOW);
+ }
+
private static NotificationChannel cloneChannel(NotificationChannel original) {
Parcel parcel = Parcel.obtain();
try {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 7d89d87..fe4ce465e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -1,5 +1,7 @@
package com.android.server.notification;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -8,6 +10,7 @@
import android.app.Application;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.service.notification.Condition;
@@ -58,6 +61,12 @@
}
@Test
+ public void getComponent_returnsComponent() {
+ assertThat(mService.getComponent()).isEqualTo(new ComponentName("android",
+ "com.android.server.notification.ScheduleConditionProvider"));
+ }
+
+ @Test
public void testIsValidConditionId_incomplete() throws Exception {
Uri badConditionId = Uri.EMPTY;
assertFalse(mService.isValidConditionId(badConditionId));
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index 129733e..f397225 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -25,99 +25,42 @@
/** AppInfo representation */
public class AppInfo implements AslMarshallable {
- private final String mTitle;
- private final String mDescription;
- private final Boolean mContainsAds;
- private final Boolean mObeyAps;
- private final Boolean mAdsFingerprinting;
- private final Boolean mSecurityFingerprinting;
+ private final Boolean mApsCompliant;
private final String mPrivacyPolicy;
- private final List<String> mSecurityEndpoints;
private final List<String> mFirstPartyEndpoints;
private final List<String> mServiceProviderEndpoints;
- private final String mCategory;
- private final String mEmail;
- private final String mWebsite;
public AppInfo(
- String title,
- String description,
- Boolean containsAds,
- Boolean obeyAps,
- Boolean adsFingerprinting,
- Boolean securityFingerprinting,
+ Boolean apsCompliant,
String privacyPolicy,
- List<String> securityEndpoints,
List<String> firstPartyEndpoints,
- List<String> serviceProviderEndpoints,
- String category,
- String email,
- String website) {
- this.mTitle = title;
- this.mDescription = description;
- this.mContainsAds = containsAds;
- this.mObeyAps = obeyAps;
- this.mAdsFingerprinting = adsFingerprinting;
- this.mSecurityFingerprinting = securityFingerprinting;
+ List<String> serviceProviderEndpoints) {
+ this.mApsCompliant = apsCompliant;
this.mPrivacyPolicy = privacyPolicy;
- this.mSecurityEndpoints = securityEndpoints;
this.mFirstPartyEndpoints = firstPartyEndpoints;
this.mServiceProviderEndpoints = serviceProviderEndpoints;
- this.mCategory = category;
- this.mEmail = email;
- this.mWebsite = website;
}
/** Creates an on-device DOM element from the {@link SafetyLabels}. */
@Override
public List<Element> toOdDomElements(Document doc) {
Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO);
- if (this.mTitle != null) {
- appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle));
- }
- if (this.mDescription != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription));
- }
- if (this.mContainsAds != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds));
- }
- if (this.mObeyAps != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps));
- }
- if (this.mAdsFingerprinting != null) {
+ if (this.mApsCompliant != null) {
appInfoEle.appendChild(
XmlUtils.createOdBooleanEle(
- doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting));
- }
- if (this.mSecurityFingerprinting != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdBooleanEle(
- doc,
- XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
- mSecurityFingerprinting));
+ doc, XmlUtils.OD_NAME_APS_COMPLIANT, mApsCompliant));
}
if (this.mPrivacyPolicy != null) {
appInfoEle.appendChild(
XmlUtils.createOdStringEle(
doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy));
}
- if (this.mSecurityEndpoints != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdArray(
- doc,
- XmlUtils.OD_TAG_STRING_ARRAY,
- XmlUtils.OD_NAME_SECURITY_ENDPOINT,
- mSecurityEndpoints));
- }
if (this.mFirstPartyEndpoints != null) {
appInfoEle.appendChild(
XmlUtils.createOdArray(
doc,
XmlUtils.OD_TAG_STRING_ARRAY,
- XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS,
mFirstPartyEndpoints));
}
if (this.mServiceProviderEndpoints != null) {
@@ -125,21 +68,9 @@
XmlUtils.createOdArray(
doc,
XmlUtils.OD_TAG_STRING_ARRAY,
- XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS,
mServiceProviderEndpoints));
}
- if (this.mCategory != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory));
- }
- if (this.mEmail != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail));
- }
- if (this.mWebsite != null) {
- appInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite));
- }
return XmlUtils.listOf(appInfoEle);
}
@@ -147,54 +78,28 @@
@Override
public List<Element> toHrDomElements(Document doc) {
Element appInfoEle = doc.createElement(XmlUtils.HR_TAG_APP_INFO);
- if (this.mTitle != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_TITLE, this.mTitle);
- }
- if (this.mDescription != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_DESCRIPTION, this.mDescription);
- }
- if (this.mContainsAds != null) {
+ if (this.mApsCompliant != null) {
appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_CONTAINS_ADS, String.valueOf(this.mContainsAds));
- }
- if (this.mObeyAps != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_OBEY_APS, String.valueOf(this.mObeyAps));
- }
- if (this.mAdsFingerprinting != null) {
- appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_ADS_FINGERPRINTING, String.valueOf(this.mAdsFingerprinting));
- }
- if (this.mSecurityFingerprinting != null) {
- appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING,
- String.valueOf(this.mSecurityFingerprinting));
+ XmlUtils.HR_ATTR_APS_COMPLIANT, String.valueOf(this.mApsCompliant));
}
if (this.mPrivacyPolicy != null) {
appInfoEle.setAttribute(XmlUtils.HR_ATTR_PRIVACY_POLICY, this.mPrivacyPolicy);
}
- if (this.mSecurityEndpoints != null) {
- appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, String.join("|", this.mSecurityEndpoints));
- }
+
if (this.mFirstPartyEndpoints != null) {
- appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS,
- String.join("|", this.mFirstPartyEndpoints));
+ appInfoEle.appendChild(
+ XmlUtils.createHrArray(
+ doc, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, mFirstPartyEndpoints));
}
+
if (this.mServiceProviderEndpoints != null) {
- appInfoEle.setAttribute(
- XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS,
- String.join("|", this.mServiceProviderEndpoints));
+ appInfoEle.appendChild(
+ XmlUtils.createHrArray(
+ doc,
+ XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS,
+ mServiceProviderEndpoints));
}
- if (this.mCategory != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_CATEGORY, this.mCategory);
- }
- if (this.mEmail != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, this.mEmail);
- }
- if (this.mWebsite != null) {
- appInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, this.mWebsite);
- }
+
return XmlUtils.listOf(appInfoEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index c506961..6ad2027 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -35,43 +35,19 @@
return null;
}
- String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE, true);
- String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION, true);
- Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, true);
- Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, true);
- Boolean adsFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, true);
- Boolean securityFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, true);
+ Boolean apsCompliant =
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_APS_COMPLIANT, true);
String privacyPolicy =
XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY, true);
- List<String> securityEndpoints =
- XmlUtils.getPipelineSplitAttr(
- appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, true);
List<String> firstPartyEndpoints =
- XmlUtils.getPipelineSplitAttr(
- appInfoEle, XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS, true);
+ XmlUtils.getHrItemsAsStrings(
+ appInfoEle, XmlUtils.HR_TAG_FIRST_PARTY_ENDPOINTS, true);
List<String> serviceProviderEndpoints =
- XmlUtils.getPipelineSplitAttr(
- appInfoEle, XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, true);
- String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY, true);
- String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
- String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+ XmlUtils.getHrItemsAsStrings(
+ appInfoEle, XmlUtils.HR_TAG_SERVICE_PROVIDER_ENDPOINTS, true);
return new AppInfo(
- title,
- description,
- containsAds,
- obeyAps,
- adsFingerprinting,
- securityFingerprinting,
- privacyPolicy,
- securityEndpoints,
- firstPartyEndpoints,
- serviceProviderEndpoints,
- category,
- email,
- website);
+ apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@@ -83,42 +59,17 @@
return null;
}
- String title = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_TITLE, true);
- String description =
- XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_DESCRIPTION, true);
- Boolean containsAds =
- XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_CONTAINS_ADS, true);
- Boolean obeyAps = XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_OBEY_APS, true);
- Boolean adsFingerprinting =
- XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_ADS_FINGERPRINTING, true);
- Boolean securityFingerprinting =
- XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_SECURITY_FINGERPRINTING, true);
+ Boolean apsCompliant =
+ XmlUtils.getOdBoolEle(appInfoEle, XmlUtils.OD_NAME_APS_COMPLIANT, true);
String privacyPolicy =
XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_PRIVACY_POLICY, true);
- List<String> securityEndpoints =
- XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_SECURITY_ENDPOINT, true);
List<String> firstPartyEndpoints =
- XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT, true);
+ XmlUtils.getOdStringArray(appInfoEle, XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINTS, true);
List<String> serviceProviderEndpoints =
XmlUtils.getOdStringArray(
- appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT, true);
- String category = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_CATEGORY, true);
- String email = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_EMAIL, true);
- String website = XmlUtils.getOdStringEle(appInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
+ appInfoEle, XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINTS, true);
return new AppInfo(
- title,
- description,
- containsAds,
- obeyAps,
- adsFingerprinting,
- securityFingerprinting,
- privacyPolicy,
- securityEndpoints,
- firstPartyEndpoints,
- serviceProviderEndpoints,
- category,
- email,
- website);
+ apsCompliant, privacyPolicy, firstPartyEndpoints, serviceProviderEndpoints);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
deleted file mode 100644
index 94fad96..0000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.asllib.marshallable;
-
-import com.android.asllib.util.XmlUtils;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.util.List;
-
-/** DeveloperInfo representation */
-public class DeveloperInfo implements AslMarshallable {
- public enum DeveloperRelationship {
- OEM(0),
- ODM(1),
- SOC(2),
- OTA(3),
- CARRIER(4),
- AOSP(5),
- OTHER(6);
-
- private final int mValue;
-
- DeveloperRelationship(int value) {
- this.mValue = value;
- }
-
- /** Get the int value associated with the DeveloperRelationship. */
- public int getValue() {
- return mValue;
- }
-
- /** Get the DeveloperRelationship associated with the int value. */
- public static DeveloperInfo.DeveloperRelationship forValue(int value) {
- for (DeveloperInfo.DeveloperRelationship e : values()) {
- if (e.getValue() == value) {
- return e;
- }
- }
- throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value);
- }
-
- /** Get the DeveloperRelationship associated with the human-readable String. */
- public static DeveloperInfo.DeveloperRelationship forString(String s) {
- for (DeveloperInfo.DeveloperRelationship e : values()) {
- if (e.toString().equals(s)) {
- return e;
- }
- }
- throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s);
- }
-
- /** Human-readable String representation of DeveloperRelationship. */
- public String toString() {
- return this.name().toLowerCase();
- }
- }
-
- private final String mName;
- private final String mEmail;
- private final String mAddress;
- private final String mCountryRegion;
- private final DeveloperRelationship mDeveloperRelationship;
- private final String mWebsite;
- private final String mAppDeveloperRegistryId;
-
- public DeveloperInfo(
- String name,
- String email,
- String address,
- String countryRegion,
- DeveloperRelationship developerRelationship,
- String website,
- String appDeveloperRegistryId) {
- this.mName = name;
- this.mEmail = email;
- this.mAddress = address;
- this.mCountryRegion = countryRegion;
- this.mDeveloperRelationship = developerRelationship;
- this.mWebsite = website;
- this.mAppDeveloperRegistryId = appDeveloperRegistryId;
- }
-
- /** Creates an on-device DOM element from the {@link SafetyLabels}. */
- @Override
- public List<Element> toOdDomElements(Document doc) {
- Element developerInfoEle =
- XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO);
- if (mName != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName));
- }
- if (mEmail != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail));
- }
- if (mAddress != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress));
- }
- if (mCountryRegion != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(
- doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion));
- }
- if (mDeveloperRelationship != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdLongEle(
- doc,
- XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
- mDeveloperRelationship.getValue()));
- }
- if (mWebsite != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite));
- }
- if (mAppDeveloperRegistryId != null) {
- developerInfoEle.appendChild(
- XmlUtils.createOdStringEle(
- doc,
- XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID,
- mAppDeveloperRegistryId));
- }
-
- return XmlUtils.listOf(developerInfoEle);
- }
-
- /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */
- @Override
- public List<Element> toHrDomElements(Document doc) {
- Element developerInfoEle = doc.createElement(XmlUtils.HR_TAG_DEVELOPER_INFO);
- if (mName != null) {
- developerInfoEle.setAttribute(XmlUtils.HR_ATTR_NAME, mName);
- }
- if (mEmail != null) {
- developerInfoEle.setAttribute(XmlUtils.HR_ATTR_EMAIL, mEmail);
- }
- if (mAddress != null) {
- developerInfoEle.setAttribute(XmlUtils.HR_ATTR_ADDRESS, mAddress);
- }
- if (mCountryRegion != null) {
- developerInfoEle.setAttribute(XmlUtils.HR_ATTR_COUNTRY_REGION, mCountryRegion);
- }
- if (mDeveloperRelationship != null) {
- developerInfoEle.setAttribute(
- XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, mDeveloperRelationship.toString());
- }
- if (mWebsite != null) {
- developerInfoEle.setAttribute(XmlUtils.HR_ATTR_WEBSITE, mWebsite);
- }
- if (mAppDeveloperRegistryId != null) {
- developerInfoEle.setAttribute(
- XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, mAppDeveloperRegistryId);
- }
-
- return XmlUtils.listOf(developerInfoEle);
- }
-}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
deleted file mode 100644
index 0f3b41c..0000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.asllib.marshallable;
-
-import com.android.asllib.util.AslgenUtil;
-import com.android.asllib.util.MalformedXmlException;
-import com.android.asllib.util.XmlUtils;
-
-import org.w3c.dom.Element;
-
-import java.util.List;
-
-public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> {
-
- /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */
- @Override
- public DeveloperInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
- Element developerInfoEle = XmlUtils.getSingleElement(elements);
- if (developerInfoEle == null) {
- AslgenUtil.logI("No DeveloperInfo found in hr format.");
- return null;
- }
- String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME, true);
- String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL, true);
- String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS, true);
- String countryRegion =
- XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION, true);
- DeveloperInfo.DeveloperRelationship developerRelationship =
- DeveloperInfo.DeveloperRelationship.forString(
- XmlUtils.getStringAttr(
- developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP, true));
- String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
- String appDeveloperRegistryId =
- XmlUtils.getStringAttr(
- developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false);
-
- return new DeveloperInfo(
- name,
- email,
- address,
- countryRegion,
- developerRelationship,
- website,
- appDeveloperRegistryId);
- }
-
- /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
- @Override
- public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException {
- Element developerInfoEle = XmlUtils.getSingleElement(elements);
- if (developerInfoEle == null) {
- AslgenUtil.logI("No DeveloperInfo found in od format.");
- return null;
- }
- String name = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_NAME, true);
- String email = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_EMAIL, true);
- String address = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_ADDRESS, true);
- String countryRegion =
- XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_COUNTRY_REGION, true);
- DeveloperInfo.DeveloperRelationship developerRelationship =
- DeveloperInfo.DeveloperRelationship.forValue(
- (int)
- (long)
- XmlUtils.getOdLongEle(
- developerInfoEle,
- XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
- true));
- String website = XmlUtils.getOdStringEle(developerInfoEle, XmlUtils.OD_NAME_WEBSITE, false);
- String appDeveloperRegistryId =
- XmlUtils.getOdStringEle(
- developerInfoEle, XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID, false);
-
- return new DeveloperInfo(
- name,
- email,
- address,
- countryRegion,
- developerRelationship,
- website,
- appDeveloperRegistryId);
- }
-}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 6af8071..2a4e130 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -25,21 +25,10 @@
/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
public class SafetyLabels implements AslMarshallable {
-
- private final Long mVersion;
private final DataLabels mDataLabels;
- private final SecurityLabels mSecurityLabels;
- private final ThirdPartyVerification mThirdPartyVerification;
- public SafetyLabels(
- Long version,
- DataLabels dataLabels,
- SecurityLabels securityLabels,
- ThirdPartyVerification thirdPartyVerification) {
- this.mVersion = version;
+ public SafetyLabels(DataLabels dataLabels) {
this.mDataLabels = dataLabels;
- this.mSecurityLabels = securityLabels;
- this.mThirdPartyVerification = thirdPartyVerification;
}
/** Returns the data label for the safety label */
@@ -47,27 +36,14 @@
return mDataLabels;
}
- /** Gets the version of the {@link SafetyLabels}. */
- public Long getVersion() {
- return mVersion;
- }
-
/** Creates an on-device DOM element from the {@link SafetyLabels}. */
@Override
public List<Element> toOdDomElements(Document doc) {
Element safetyLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
- safetyLabelsEle.appendChild(
- XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
if (mDataLabels != null) {
XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
}
- if (mSecurityLabels != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toOdDomElements(doc));
- }
- if (mThirdPartyVerification != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toOdDomElements(doc));
- }
return XmlUtils.listOf(safetyLabelsEle);
}
@@ -75,17 +51,10 @@
@Override
public List<Element> toHrDomElements(Document doc) {
Element safetyLabelsEle = doc.createElement(XmlUtils.HR_TAG_SAFETY_LABELS);
- safetyLabelsEle.setAttribute(XmlUtils.HR_ATTR_VERSION, String.valueOf(mVersion));
if (mDataLabels != null) {
XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toHrDomElements(doc));
}
- if (mSecurityLabels != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mSecurityLabels.toHrDomElements(doc));
- }
- if (mThirdPartyVerification != null) {
- XmlUtils.appendChildren(safetyLabelsEle, mThirdPartyVerification.toHrDomElements(doc));
- }
return XmlUtils.listOf(safetyLabelsEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index 2644b43..2738337 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -34,7 +34,6 @@
AslgenUtil.logI("No SafetyLabels found in hr format.");
return null;
}
- long version = XmlUtils.tryGetVersion(safetyLabelsEle);
DataLabels dataLabels =
new DataLabelsFactory()
@@ -44,23 +43,7 @@
safetyLabelsEle,
XmlUtils.HR_TAG_DATA_LABELS,
false)));
- SecurityLabels securityLabels =
- new SecurityLabelsFactory()
- .createFromHrElements(
- XmlUtils.listOf(
- XmlUtils.getSingleChildElement(
- safetyLabelsEle,
- XmlUtils.HR_TAG_SECURITY_LABELS,
- false)));
- ThirdPartyVerification thirdPartyVerification =
- new ThirdPartyVerificationFactory()
- .createFromHrElements(
- XmlUtils.listOf(
- XmlUtils.getSingleChildElement(
- safetyLabelsEle,
- XmlUtils.HR_TAG_THIRD_PARTY_VERIFICATION,
- false)));
- return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
+ return new SafetyLabels(dataLabels);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@@ -71,7 +54,6 @@
AslgenUtil.logI("No SafetyLabels found in od format.");
return null;
}
- Long version = XmlUtils.getOdLongEle(safetyLabelsEle, XmlUtils.OD_NAME_VERSION, true);
DataLabels dataLabels =
new DataLabelsFactory()
@@ -81,22 +63,7 @@
safetyLabelsEle,
XmlUtils.OD_NAME_DATA_LABELS,
false)));
- SecurityLabels securityLabels =
- new SecurityLabelsFactory()
- .createFromOdElements(
- XmlUtils.listOf(
- XmlUtils.getOdPbundleWithName(
- safetyLabelsEle,
- XmlUtils.OD_NAME_SECURITY_LABELS,
- false)));
- ThirdPartyVerification thirdPartyVerification =
- new ThirdPartyVerificationFactory()
- .createFromOdElements(
- XmlUtils.listOf(
- XmlUtils.getOdPbundleWithName(
- safetyLabelsEle,
- XmlUtils.OD_NAME_THIRD_PARTY_VERIFICATION,
- false)));
- return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification);
+
+ return new SafetyLabels(dataLabels);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index 6a8700a..9f789f0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -23,22 +23,14 @@
import java.util.List;
-/** TransparencyInfo representation containing {@link DeveloperInfo} and {@link AppInfo} */
+/** TransparencyInfo representation containing {@link AppInfo} */
public class TransparencyInfo implements AslMarshallable {
-
- private final DeveloperInfo mDeveloperInfo;
private final AppInfo mAppInfo;
- public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) {
- this.mDeveloperInfo = developerInfo;
+ public TransparencyInfo(AppInfo appInfo) {
this.mAppInfo = appInfo;
}
- /** Gets the {@link DeveloperInfo} of the {@link TransparencyInfo}. */
- public DeveloperInfo getDeveloperInfo() {
- return mDeveloperInfo;
- }
-
/** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */
public AppInfo getAppInfo() {
return mAppInfo;
@@ -49,9 +41,6 @@
public List<Element> toOdDomElements(Document doc) {
Element transparencyInfoEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO);
- if (mDeveloperInfo != null) {
- XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toOdDomElements(doc));
- }
if (mAppInfo != null) {
XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc));
}
@@ -62,9 +51,6 @@
@Override
public List<Element> toHrDomElements(Document doc) {
Element transparencyInfoEle = doc.createElement(XmlUtils.HR_TAG_TRANSPARENCY_INFO);
- if (mDeveloperInfo != null) {
- XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toHrDomElements(doc));
- }
if (mAppInfo != null) {
XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toHrDomElements(doc));
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 94c5640..40f2872 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -36,18 +36,11 @@
return null;
}
- Element developerInfoEle =
- XmlUtils.getSingleChildElement(
- transparencyInfoEle, XmlUtils.HR_TAG_DEVELOPER_INFO, false);
- DeveloperInfo developerInfo =
- new DeveloperInfoFactory().createFromHrElements(XmlUtils.listOf(developerInfoEle));
-
Element appInfoEle =
- XmlUtils.getSingleChildElement(
- transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, false);
+ XmlUtils.getSingleChildElement(transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, true);
AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle));
- return new TransparencyInfo(developerInfo, appInfo);
+ return new TransparencyInfo(appInfo);
}
/** Creates an {@link AslMarshallableFactory} from on-device DOM elements */
@@ -60,17 +53,10 @@
return null;
}
- Element developerInfoEle =
- XmlUtils.getOdPbundleWithName(
- transparencyInfoEle, XmlUtils.OD_NAME_DEVELOPER_INFO, false);
- DeveloperInfo developerInfo =
- new DeveloperInfoFactory().createFromOdElements(XmlUtils.listOf(developerInfoEle));
-
Element appInfoEle =
- XmlUtils.getOdPbundleWithName(
- transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, false);
+ XmlUtils.getOdPbundleWithName(transparencyInfoEle, XmlUtils.OD_NAME_APP_INFO, true);
AppInfo appInfo = new AppInfoFactory().createFromOdElements(XmlUtils.listOf(appInfoEle));
- return new TransparencyInfo(developerInfo, appInfo);
+ return new TransparencyInfo(appInfo);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index 97cbc39..26b5639 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -42,6 +42,7 @@
public static final String HR_TAG_DATA_COLLECTED = "data-collected";
public static final String HR_TAG_DATA_COLLECTED_EPHEMERAL = "data-collected-ephemeral";
public static final String HR_TAG_DATA_SHARED = "data-shared";
+ public static final String HR_TAG_ITEM = "item";
public static final String HR_ATTR_NAME = "name";
public static final String HR_ATTR_EMAIL = "email";
public static final String HR_ATTR_ADDRESS = "address";
@@ -64,12 +65,13 @@
public static final String HR_ATTR_DESCRIPTION = "description";
public static final String HR_ATTR_CONTAINS_ADS = "containsAds";
public static final String HR_ATTR_OBEY_APS = "obeyAps";
+ public static final String HR_ATTR_APS_COMPLIANT = "apsCompliant";
public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting";
public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting";
public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy";
public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints";
- public static final String HR_ATTR_FIRST_PARTY_ENDPOINTS = "firstPartyEndpoints";
- public static final String HR_ATTR_SERVICE_PROVIDER_ENDPOINTS = "serviceProviderEndpoints";
+ public static final String HR_TAG_FIRST_PARTY_ENDPOINTS = "first-party-endpoints";
+ public static final String HR_TAG_SERVICE_PROVIDER_ENDPOINTS = "service-provider-endpoints";
public static final String HR_ATTR_CATEGORY = "category";
public static final String OD_TAG_BUNDLE = "bundle";
@@ -98,12 +100,13 @@
public static final String OD_NAME_DESCRIPTION = "description";
public static final String OD_NAME_CONTAINS_ADS = "contains_ads";
public static final String OD_NAME_OBEY_APS = "obey_aps";
+ public static final String OD_NAME_APS_COMPLIANT = "aps_compliant";
public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting";
public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting";
public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy";
- public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoint";
- public static final String OD_NAME_FIRST_PARTY_ENDPOINT = "first_party_endpoint";
- public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINT = "service_provider_endpoint";
+ public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoints";
+ public static final String OD_NAME_FIRST_PARTY_ENDPOINTS = "first_party_endpoints";
+ public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINTS = "service_provider_endpoints";
public static final String OD_NAME_CATEGORY = "category";
public static final String OD_NAME_VERSION = "version";
public static final String OD_NAME_URL = "url";
@@ -237,7 +240,18 @@
return ele;
}
- /** Create OD style array DOM Element, which can represent any time but is stored as Strings. */
+ /** Create HR style array DOM Element. */
+ public static Element createHrArray(Document doc, String arrayTagName, List<String> arrayVals) {
+ Element arrEle = doc.createElement(arrayTagName);
+ for (String s : arrayVals) {
+ Element itemEle = doc.createElement(XmlUtils.HR_TAG_ITEM);
+ itemEle.setTextContent(s);
+ arrEle.appendChild(itemEle);
+ }
+ return arrEle;
+ }
+
+ /** Create OD style array DOM Element, which can represent any type but is stored as Strings. */
public static Element createOdArray(
Document doc, String arrayTag, String arrayName, List<String> arrayVals) {
Element arrEle = doc.createElement(arrayTag);
@@ -456,6 +470,32 @@
return ints;
}
+ /** Gets human-readable style String array. */
+ public static List<String> getHrItemsAsStrings(
+ Element parent, String elementName, boolean required) throws MalformedXmlException {
+
+ List<Element> arrayEles = XmlUtils.getChildrenByTagName(parent, elementName);
+ if (arrayEles.size() > 1) {
+ throw new MalformedXmlException(
+ String.format(
+ "Found more than one %s in %s.", elementName, parent.getTagName()));
+ }
+ if (arrayEles.isEmpty()) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no %s in %s.", elementName, parent.getTagName()));
+ }
+ return null;
+ }
+ Element arrayEle = arrayEles.get(0);
+ List<Element> itemEles = XmlUtils.getChildrenByTagName(arrayEle, XmlUtils.HR_TAG_ITEM);
+ List<String> strs = new ArrayList<String>();
+ for (Element itemEle : itemEles) {
+ strs.add(itemEle.getTextContent());
+ }
+ return strs;
+ }
+
/** Gets on-device style String array. */
public static List<String> getOdStringArray(Element ele, String nameName, boolean required)
throws MalformedXmlException {
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
index dbeeb49..14e65e5 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -20,11 +20,8 @@
import com.android.asllib.marshallable.AppInfoTest;
import com.android.asllib.marshallable.DataLabelsTest;
import com.android.asllib.marshallable.DataTypeEqualityTest;
-import com.android.asllib.marshallable.DeveloperInfoTest;
import com.android.asllib.marshallable.SafetyLabelsTest;
-import com.android.asllib.marshallable.SecurityLabelsTest;
import com.android.asllib.marshallable.SystemAppSafetyLabelTest;
-import com.android.asllib.marshallable.ThirdPartyVerificationTest;
import com.android.asllib.marshallable.TransparencyInfoTest;
import org.junit.runner.RunWith;
@@ -35,14 +32,10 @@
AslgenTests.class,
AndroidSafetyLabelTest.class,
AppInfoTest.class,
- // DataCategoryTest.class,
DataLabelsTest.class,
DataTypeEqualityTest.class,
- DeveloperInfoTest.class,
SafetyLabelsTest.class,
- SecurityLabelsTest.class,
SystemAppSafetyLabelTest.class,
- ThirdPartyVerificationTest.class,
TransparencyInfoTest.class
})
public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index 9e91c6f..d823c48 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -20,6 +20,7 @@
import com.android.asllib.testutils.TestUtils;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.junit.Before;
import org.junit.Test;
@@ -34,35 +35,15 @@
private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr";
private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od";
public static final List<String> REQUIRED_FIELD_NAMES =
- List.of(
- "title",
- "description",
- "containsAds",
- "obeyAps",
- "adsFingerprinting",
- "securityFingerprinting",
- "privacyPolicy",
- "securityEndpoints",
- "firstPartyEndpoints",
- "serviceProviderEndpoints",
- "category",
- "email");
+ List.of("apsCompliant", "privacyPolicy");
public static final List<String> REQUIRED_FIELD_NAMES_OD =
- List.of(
- "title",
- "description",
- "contains_ads",
- "obey_aps",
- "ads_fingerprinting",
- "security_fingerprinting",
- "privacy_policy",
- "security_endpoint",
- "first_party_endpoint",
- "service_provider_endpoint",
- "category",
- "email");
- public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website");
- public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of("website");
+ List.of("aps_compliant", "privacy_policy");
+ public static final List<String> REQUIRED_CHILD_NAMES =
+ List.of("first-party-endpoints", "service-provider-endpoints");
+ public static final List<String> REQUIRED_CHILD_NAMES_OD =
+ List.of("first_party_endpoints", "service_provider_endpoints");
+ public static final List<String> OPTIONAL_FIELD_NAMES = List.of();
+ public static final List<String> OPTIONAL_FIELD_NAMES_OD = List.of();
private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
@@ -110,6 +91,34 @@
}
}
+ /** Tests missing required child fails. */
+ @Test
+ public void testMissingRequiredChild() throws Exception {
+ System.out.println("Starting testMissingRequiredFields");
+ for (String reqChildName : REQUIRED_CHILD_NAMES) {
+ System.out.println("testing missing required child hr: " + reqChildName);
+ var appInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ var child = XmlUtils.getChildrenByTagName(appInfoEle.get(0), reqChildName).get(0);
+ appInfoEle.get(0).removeChild(child);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+ }
+
+ for (String reqField : REQUIRED_CHILD_NAMES_OD) {
+ System.out.println("testing missing required child od: " + reqField);
+ var appInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ TestUtils.removeOdChildEleWithName(appInfoEle.get(0), reqField);
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromOdElements(appInfoEle));
+ }
+ }
+
/** Tests missing optional fields passes. */
@Test
public void testMissingOptionalFields() throws Exception {
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
deleted file mode 100644
index 72e8d65..0000000
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2017 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.asllib.marshallable;
-
-import static org.junit.Assert.assertThrows;
-
-import com.android.asllib.testutils.TestUtils;
-import com.android.asllib.util.MalformedXmlException;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.nio.file.Paths;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class DeveloperInfoTest {
- private static final String DEVELOPER_INFO_HR_PATH = "com/android/asllib/developerinfo/hr";
- private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od";
- public static final List<String> REQUIRED_FIELD_NAMES =
- List.of("address", "countryRegion", "email", "name", "relationship");
- public static final List<String> REQUIRED_FIELD_NAMES_OD =
- List.of("address", "country_region", "email", "name", "relationship");
- public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId");
- public static final List<String> OPTIONAL_FIELD_NAMES_OD =
- List.of("website", "app_developer_registry_id");
-
- private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
-
- /** Logic for setting up tests (empty if not yet needed). */
- public static void main(String[] params) throws Exception {}
-
- @Before
- public void setUp() throws Exception {
- System.out.println("set up.");
- }
-
- /** Test for all fields valid. */
- @Test
- public void testAllFieldsValid() throws Exception {
- System.out.println("starting testAllFieldsValid.");
- testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
- testOdToHrDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
- }
-
- /** Tests missing required fields fails. */
- @Test
- public void testMissingRequiredFields() throws Exception {
- System.out.println("Starting testMissingRequiredFields");
- for (String reqField : REQUIRED_FIELD_NAMES) {
- System.out.println("testing missing required field: " + reqField);
- var developerInfoEle =
- TestUtils.getElementsFromResource(
- Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- developerInfoEle.get(0).removeAttribute(reqField);
-
- assertThrows(
- MalformedXmlException.class,
- () -> new DeveloperInfoFactory().createFromHrElements(developerInfoEle));
- }
-
- for (String reqField : REQUIRED_FIELD_NAMES_OD) {
- System.out.println("testing missing required field od: " + reqField);
- var developerInfoEle =
- TestUtils.getElementsFromResource(
- Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), reqField);
-
- assertThrows(
- MalformedXmlException.class,
- () -> new DeveloperInfoFactory().createFromOdElements(developerInfoEle));
- }
- }
-
- /** Tests missing optional fields passes. */
- @Test
- public void testMissingOptionalFields() throws Exception {
- for (String optField : OPTIONAL_FIELD_NAMES) {
- var developerInfoEle =
- TestUtils.getElementsFromResource(
- Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- developerInfoEle.get(0).removeAttribute(optField);
- DeveloperInfo developerInfo =
- new DeveloperInfoFactory().createFromHrElements(developerInfoEle);
- developerInfo.toOdDomElements(TestUtils.document());
- }
-
- for (String optField : OPTIONAL_FIELD_NAMES_OD) {
- var developerInfoEle =
- TestUtils.getElementsFromResource(
- Paths.get(DEVELOPER_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(developerInfoEle.get(0), optField);
- DeveloperInfo developerInfo =
- new DeveloperInfoFactory().createFromOdElements(developerInfoEle);
- developerInfo.toHrDomElements(TestUtils.document());
- }
- }
-
- private void testHrToOdDeveloperInfo(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new DeveloperInfoFactory(),
- DEVELOPER_INFO_HR_PATH,
- DEVELOPER_INFO_OD_PATH,
- fileName);
- }
-
- private void testOdToHrDeveloperInfo(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new DeveloperInfoFactory(),
- DEVELOPER_INFO_OD_PATH,
- DEVELOPER_INFO_HR_PATH,
- fileName);
- }
-}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
index bba6b54..19d1626 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -28,26 +28,14 @@
private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr";
private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od";
- private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml";
- private static final String WITH_SECURITY_LABELS_FILE_NAME = "with-security-labels.xml";
- private static final String WITH_THIRD_PARTY_VERIFICATION_FILE_NAME =
- "with-third-party-verification.xml";
@Before
public void setUp() throws Exception {
System.out.println("set up.");
}
- /** Test for safety labels missing version. */
- @Test
- public void testSafetyLabelsMissingVersion() throws Exception {
- System.out.println("starting testSafetyLabelsMissingVersion.");
- hrToOdExpectException(MISSING_VERSION_FILE_NAME);
- odToHrExpectException(MISSING_VERSION_FILE_NAME);
- }
-
/** Test for safety labels valid empty. */
@Test
public void testSafetyLabelsValidEmptyFile() throws Exception {
@@ -64,22 +52,6 @@
testOdToHrSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
}
- /** Test for safety labels with security labels. */
- @Test
- public void testSafetyLabelsWithSecurityLabels() throws Exception {
- System.out.println("starting testSafetyLabelsWithSecurityLabels.");
- testHrToOdSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
- testOdToHrSafetyLabels(WITH_SECURITY_LABELS_FILE_NAME);
- }
-
- /** Test for safety labels with third party verification. */
- @Test
- public void testSafetyLabelsWithThirdPartyVerification() throws Exception {
- System.out.println("starting testSafetyLabelsWithThirdPartyVerification.");
- testHrToOdSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
- testOdToHrSafetyLabels(WITH_THIRD_PARTY_VERIFICATION_FILE_NAME);
- }
-
private void hrToOdExpectException(String fileName) {
TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
deleted file mode 100644
index a940bc6..0000000
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 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.asllib.marshallable;
-
-
-import com.android.asllib.testutils.TestUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.nio.file.Paths;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class SecurityLabelsTest {
- private static final String SECURITY_LABELS_HR_PATH = "com/android/asllib/securitylabels/hr";
- private static final String SECURITY_LABELS_OD_PATH = "com/android/asllib/securitylabels/od";
-
- public static final List<String> OPTIONAL_FIELD_NAMES =
- List.of("isDataDeletable", "isDataEncrypted");
- public static final List<String> OPTIONAL_FIELD_NAMES_OD =
- List.of("is_data_deletable", "is_data_encrypted");
-
- private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
-
- /** Logic for setting up tests (empty if not yet needed). */
- public static void main(String[] params) throws Exception {}
-
- @Before
- public void setUp() throws Exception {
- System.out.println("set up.");
- }
-
- /** Test for all fields valid. */
- @Test
- public void testAllFieldsValid() throws Exception {
- System.out.println("starting testAllFieldsValid.");
- testHrToOdSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
- testOdToHrSecurityLabels(ALL_FIELDS_VALID_FILE_NAME);
- }
-
- /** Tests missing optional fields passes. */
- @Test
- public void testMissingOptionalFields() throws Exception {
- for (String optField : OPTIONAL_FIELD_NAMES) {
- var ele =
- TestUtils.getElementsFromResource(
- Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
- ele.get(0).removeAttribute(optField);
- SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElements(ele);
- securityLabels.toOdDomElements(TestUtils.document());
- }
- for (String optField : OPTIONAL_FIELD_NAMES_OD) {
- var ele =
- TestUtils.getElementsFromResource(
- Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
- TestUtils.removeOdChildEleWithName(ele.get(0), optField);
- SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElements(ele);
- securityLabels.toHrDomElements(TestUtils.document());
- }
- }
-
- private void testHrToOdSecurityLabels(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new SecurityLabelsFactory(),
- SECURITY_LABELS_HR_PATH,
- SECURITY_LABELS_OD_PATH,
- fileName);
- }
-
- private void testOdToHrSecurityLabels(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new SecurityLabelsFactory(),
- SECURITY_LABELS_OD_PATH,
- SECURITY_LABELS_HR_PATH,
- fileName);
- }
-}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
deleted file mode 100644
index ec86d0f..0000000
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/ThirdPartyVerificationTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 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.asllib.marshallable;
-
-import com.android.asllib.testutils.TestUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class ThirdPartyVerificationTest {
- private static final String THIRD_PARTY_VERIFICATION_HR_PATH =
- "com/android/asllib/thirdpartyverification/hr";
- private static final String THIRD_PARTY_VERIFICATION_OD_PATH =
- "com/android/asllib/thirdpartyverification/od";
-
- private static final String VALID_FILE_NAME = "valid.xml";
- private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
-
- /** Logic for setting up tests (empty if not yet needed). */
- public static void main(String[] params) throws Exception {}
-
- @Before
- public void setUp() throws Exception {
- System.out.println("set up.");
- }
-
- /** Test for valid. */
- @Test
- public void testValid() throws Exception {
- System.out.println("starting testValid.");
- testHrToOdThirdPartyVerification(VALID_FILE_NAME);
- testOdToHrThirdPartyVerification(VALID_FILE_NAME);
- }
-
- /** Tests missing url. */
- @Test
- public void testMissingUrl() throws Exception {
- System.out.println("starting testMissingUrl.");
- hrToOdExpectException(MISSING_URL_FILE_NAME);
- odToHrExpectException(MISSING_URL_FILE_NAME);
- }
-
- private void hrToOdExpectException(String fileName) {
- TestUtils.hrToOdExpectException(
- new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_HR_PATH, fileName);
- }
-
- private void odToHrExpectException(String fileName) {
- TestUtils.odToHrExpectException(
- new ThirdPartyVerificationFactory(), THIRD_PARTY_VERIFICATION_OD_PATH, fileName);
- }
-
- private void testHrToOdThirdPartyVerification(String fileName) throws Exception {
- TestUtils.testHrToOd(
- TestUtils.document(),
- new ThirdPartyVerificationFactory(),
- THIRD_PARTY_VERIFICATION_HR_PATH,
- THIRD_PARTY_VERIFICATION_OD_PATH,
- fileName);
- }
-
- private void testOdToHrThirdPartyVerification(String fileName) throws Exception {
- TestUtils.testOdToHr(
- TestUtils.document(),
- new ThirdPartyVerificationFactory(),
- THIRD_PARTY_VERIFICATION_OD_PATH,
- THIRD_PARTY_VERIFICATION_HR_PATH,
- fileName);
- }
-}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
index f494240..8a0b35e 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -29,9 +29,6 @@
"com/android/asllib/transparencyinfo/hr";
private static final String TRANSPARENCY_INFO_OD_PATH =
"com/android/asllib/transparencyinfo/od";
-
- private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
- private static final String WITH_DEVELOPER_INFO_FILE_NAME = "with-developer-info.xml";
private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml";
@Before
@@ -39,22 +36,6 @@
System.out.println("set up.");
}
- /** Test for transparency info valid empty. */
- @Test
- public void testTransparencyInfoValidEmptyFile() throws Exception {
- System.out.println("starting testTransparencyInfoValidEmptyFile.");
- testHrToOdTransparencyInfo(VALID_EMPTY_FILE_NAME);
- testOdToHrTransparencyInfo(VALID_EMPTY_FILE_NAME);
- }
-
- /** Test for transparency info with developer info. */
- @Test
- public void testTransparencyInfoWithDeveloperInfo() throws Exception {
- System.out.println("starting testTransparencyInfoWithDeveloperInfo.");
- testHrToOdTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
- testOdToHrTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
- }
-
/** Test for transparency info with app info. */
@Test
public void testTransparencyInfoWithAppInfo() throws Exception {
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
index 53794a1..03e71d2 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
@@ -1,4 +1,4 @@
<app-metadata-bundles version="123456">
- <safety-labels version="12345">
+ <safety-labels>
</safety-labels>
</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
index 00bcfa8..a00ef65 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
@@ -1,4 +1,15 @@
<app-metadata-bundles version="123456">
-<transparency-info>
-</transparency-info>
+ <transparency-info>
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
+ </transparency-info>
</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
index 74644ed..f00fb26 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
@@ -1,6 +1,5 @@
<bundle>
<long name="version" value="123456"/>
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
</pbundle_as_map>
</bundle>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
index 63c5094..d0c8668 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
@@ -1,4 +1,16 @@
<bundle>
<long name="version" value="123456"/>
- <pbundle_as_map name="transparency_info"/>
+ <pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <boolean name="aps_compliant" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="first_party_endpoints" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoints" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ </pbundle_as_map>
+ </pbundle_as_map>
</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
index 883170a2..0d15efc 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
@@ -1,14 +1,11 @@
<app-info
- title="beervision"
- description="a beer app"
- containsAds="true"
- obeyAps="false"
- adsFingerprinting="false"
- securityFingerprinting="false"
- privacyPolicy="www.example.com"
- securityEndpoints="url1|url2|url3"
- firstPartyEndpoints="url1"
- serviceProviderEndpoints="url55|url56"
- category="Food and drink"
- email="max@maxloh.com"
- website="www.example.com" />
\ No newline at end of file
+ apsCompliant="false"
+ privacyPolicy="www.example.com">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+</app-info>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
index 6e976a3..bce5179 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
@@ -1,25 +1,12 @@
<pbundle_as_map name="app_info">
- <string name="title" value="beervision"/>
- <string name="description" value="a beer app"/>
- <boolean name="contains_ads" value="true"/>
- <boolean name="obey_aps" value="false"/>
- <boolean name="ads_fingerprinting" value="false"/>
- <boolean name="security_fingerprinting" value="false"/>
+ <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
- <string-array name="security_endpoint" num="3">
- <item value="url1"/>
- <item value="url2"/>
- <item value="url3"/>
- </string-array>
- <string-array name="first_party_endpoint" num="1">
+ <string-array name="first_party_endpoints" num="1">
<item value="url1"/>
</string-array>
- <string-array name="service_provider_endpoint" num="2">
+ <string-array name="service_provider_endpoints" num="2">
<item value="url55"/>
<item value="url56"/>
</string-array>
- <string name="category" value="Food and drink"/>
- <string name="email" value="max@maxloh.com"/>
- <string name="website" value="www.example.com"/>
</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
deleted file mode 100644
index 762f3bd..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<safety-labels>
-</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
index 7decfd4..92d10ca 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
@@ -1 +1 @@
-<safety-labels version="12345"></safety-labels>
\ No newline at end of file
+<safety-labels></safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
index 84456da..ca3d9f0 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
@@ -1,4 +1,4 @@
-<safety-labels version="12345">
+<safety-labels>
<data-labels>
<data-shared dataType="location_data_type_approx_location"
isSharingOptional="false"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
index 940e48a..c962785 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-security-labels.xml
@@ -1,4 +1,4 @@
-<safety-labels version="12345">
+<safety-labels>
<security-labels
isDataDeletable="true"
isDataEncrypted="false"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
index bfbc5ae..0805290 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-third-party-verification.xml
@@ -1,4 +1,4 @@
-<safety-labels version="12345">
+<safety-labels>
<third-party-verification url="www.example.com">
</third-party-verification>
</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml
deleted file mode 100644
index 3fbe359..0000000
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/missing-version.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<pbundle_as_map name="safety_labels">
-</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
index 4f03d88..3fbe359 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
@@ -1,3 +1,2 @@
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
index fa2a3f8..db92e02 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
@@ -1,5 +1,4 @@
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="data_labels">
<pbundle_as_map name="data_shared">
<pbundle_as_map name="location">
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
index b39c562b..9246180 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-security-labels.xml
@@ -1,5 +1,4 @@
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="security_labels">
<boolean name="is_data_deletable" value="true" />
<boolean name="is_data_encrypted" value="false" />
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
index 10653ff..fd435c5 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-third-party-verification.xml
@@ -1,5 +1,4 @@
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="third_party_verification">
<string name="url" value="www.example.com"/>
</pbundle_as_map>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
index a7c48fc..2512ca4 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
@@ -1,4 +1,14 @@
<transparency-info>
- <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+ <app-info
+ apsCompliant="false"
+ privacyPolicy="www.example.com">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
</transparency-info>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
index b813641..c7bdd97 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
@@ -1,26 +1,14 @@
<pbundle_as_map name="transparency_info">
<pbundle_as_map name="app_info">
- <string name="title" value="beervision"/>
- <string name="description" value="a beer app"/>
- <boolean name="contains_ads" value="true"/>
- <boolean name="obey_aps" value="false"/>
- <boolean name="ads_fingerprinting" value="false"/>
- <boolean name="security_fingerprinting" value="false"/>
+ <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
- <string-array name="security_endpoint" num="3">
- <item value="url1"/>
- <item value="url2"/>
- <item value="url3"/>
- </string-array>
- <string-array name="first_party_endpoint" num="1">
+ <string-array name="first_party_endpoints" num="1">
<item value="url1"/>
</string-array>
- <string-array name="service_provider_endpoint" num="2">
+ <string-array name="service_provider_endpoints" num="2">
<item value="url55"/>
<item value="url56"/>
</string-array>
- <string name="category" value="Food and drink"/>
- <string name="email" value="max@maxloh.com"/>
</pbundle_as_map>
</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
index b2ff449..cadf213 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
@@ -1,5 +1,5 @@
<app-metadata-bundles version="123">
- <safety-labels version="12345">
+ <safety-labels>
<data-labels>
<data-shared dataCategory="contacts"
dataType="contacts"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
index 81277bf..7aafd23 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
@@ -1,7 +1,6 @@
<bundle>
<long name="version" value="123"/>
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="data_labels">
<pbundle_as_map name="data_shared">
<pbundle_as_map name="contacts">
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
index 41b32b5..5923079 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/hr.xml
@@ -1,5 +1,5 @@
<app-metadata-bundles version="123">
- <safety-labels version="12345">
+ <safety-labels>
<data-labels>
<data-shared
dataType="location_data_type_approx_location"
@@ -10,24 +10,18 @@
isSharingOptional="true"
purposes="app_functionality|analytics" />
</data-labels>
- <security-labels
- isDataDeletable="true"
- isDataEncrypted="false"
- />
- <third-party-verification url="www.example.com">
- </third-party-verification>
</safety-labels>
<system-app-safety-label declaration="true">
</system-app-safety-label>
<transparency-info>
- <developer-info
- name="max"
- email="max@example.com"
- address="111 blah lane"
- countryRegion="US"
- relationship="aosp"
- website="example.com"
- registryId="registry_id" />
- <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+ <app-info apsCompliant="false" privacyPolicy="www.example.com">
+ <first-party-endpoints>
+ <item>url1</item>
+ </first-party-endpoints>
+ <service-provider-endpoints>
+ <item>url55</item>
+ <item>url56</item>
+ </service-provider-endpoints>
+ </app-info>
</transparency-info>
</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
index c11ac43..c24087e 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/general/od.xml
@@ -1,7 +1,6 @@
<bundle>
<long name="version" value="123"/>
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="data_labels">
<pbundle_as_map name="data_shared">
<pbundle_as_map name="location">
@@ -21,49 +20,21 @@
</pbundle_as_map>
</pbundle_as_map>
</pbundle_as_map>
- <pbundle_as_map name="security_labels">
- <boolean name="is_data_deletable" value="true"/>
- <boolean name="is_data_encrypted" value="false"/>
- </pbundle_as_map>
- <pbundle_as_map name="third_party_verification">
- <string name="url" value="www.example.com"/>
- </pbundle_as_map>
</pbundle_as_map>
<pbundle_as_map name="system_app_safety_label">
<boolean name="declaration" value="true"/>
</pbundle_as_map>
<pbundle_as_map name="transparency_info">
- <pbundle_as_map name="developer_info">
- <string name="name" value="max"/>
- <string name="email" value="max@example.com"/>
- <string name="address" value="111 blah lane"/>
- <string name="country_region" value="US"/>
- <long name="relationship" value="5"/>
- <string name="website" value="example.com"/>
- <string name="app_developer_registry_id" value="registry_id"/>
- </pbundle_as_map>
<pbundle_as_map name="app_info">
- <string name="title" value="beervision"/>
- <string name="description" value="a beer app"/>
- <boolean name="contains_ads" value="true"/>
- <boolean name="obey_aps" value="false"/>
- <boolean name="ads_fingerprinting" value="false"/>
- <boolean name="security_fingerprinting" value="false"/>
+ <boolean name="aps_compliant" value="false"/>
<string name="privacy_policy" value="www.example.com"/>
- <string-array name="security_endpoint" num="3">
- <item value="url1"/>
- <item value="url2"/>
- <item value="url3"/>
- </string-array>
- <string-array name="first_party_endpoint" num="1">
+ <string-array name="first_party_endpoints" num="1">
<item value="url1"/>
</string-array>
- <string-array name="service_provider_endpoint" num="2">
+ <string-array name="service_provider_endpoints" num="2">
<item value="url55"/>
<item value="url56"/>
</string-array>
- <string name="category" value="Food and drink"/>
- <string name="email" value="max@maxloh.com"/>
</pbundle_as_map>
</pbundle_as_map>
</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
index ac844b3..a4242d0 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
@@ -1,5 +1,5 @@
<app-metadata-bundles version="123">
- <safety-labels version="12345">
+ <safety-labels>
<data-labels>
<data-shared dataCategory="location"
dataType="precise_location"
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
index d0a3bfa..79afaf7 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
@@ -1,7 +1,6 @@
<bundle>
<long name="version" value="123"/>
<pbundle_as_map name="safety_labels">
- <long name="version" value="12345"/>
<pbundle_as_map name="data_labels">
<pbundle_as_map name="data_shared">
<pbundle_as_map name="location">