summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/Intent.java11
-rw-r--r--core/java/android/permission/flags.aconfig4
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig15
-rw-r--r--core/java/android/service/quickaccesswallet/flags.aconfig7
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp4
-rw-r--r--core/res/AndroidManifest.xml11
-rw-r--r--core/res/res/layout/notification_template_header.xml33
-rw-r--r--core/res/res/layout/notification_template_material_base.xml40
-rw-r--r--core/res/res/layout/notification_template_material_media.xml8
-rw-r--r--core/res/res/layout/notification_template_material_messaging.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--nfc/api/system-current.txt3
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java19
-rw-r--r--packages/StatementService/Android.bp2
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/Converters.kt130
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt27
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt36
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt41
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt4
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt21
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt14
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt55
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt46
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt10
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt7
-rw-r--r--packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt19
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java2
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt145
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java91
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java31
-rw-r--r--services/core/java/com/android/server/am/OWNERS3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java15
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java9
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java12
46 files changed, 1034 insertions, 208 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 02eed1a7553f..3d2d487b9d06 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7720,7 +7720,6 @@ public class Intent implements Parcelable, Cloneable {
@IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
EXTENDED_FLAG_FILTER_MISMATCH,
EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,
- EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExtendedFlags {}
@@ -7741,13 +7740,6 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1;
- /**
- * This flag indicates this intent called {@link #collectExtraIntentKeys()}.
- *
- * @hide
- */
- public static final int EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED = 1 << 2;
-
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -12336,8 +12328,7 @@ public class Intent implements Parcelable, Cloneable {
}
private void collectNestedIntentKeysRecur(Set<Intent> visited) {
- addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
- if (mExtras != null && !mExtras.isEmpty()) {
+ if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
Object value = mExtras.get(key);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 55ba4afc9c68..a653e0a493ee 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -58,7 +58,7 @@ flag {
is_fixed_read_only: true
namespace: "permissions"
description: "enable enhanced confirmation incall apis"
- bug: "310220212"
+ bug: "364535720"
}
flag {
@@ -67,7 +67,7 @@ flag {
is_fixed_read_only: true
namespace: "permissions"
description: "enable the blocking of certain app installs during an unknown call"
- bug: "310220212"
+ bug: "364535720"
}
flag {
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 357aba3fb136..6c92991ceff6 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -96,21 +96,6 @@ flag {
}
flag {
- name: "prevent_intent_redirect_throw_exception_if_nested_keys_not_collected"
- namespace: "responsible_apis"
- description: "Prevent intent redirect attacks by throwing exception if the intent does not collect nested keys"
- bug: "361143368"
-}
-
-flag {
- name: "prevent_intent_redirect_collect_nested_keys_on_server_if_not_collected"
- namespace: "responsible_apis"
- description: "Prevent intent redirect attacks by collecting nested keys on server if not yet collected"
- bug: "361143368"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_intent_matching_flags"
is_exported: true
namespace: "permissions"
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 75a93091eec3..7225f27c4555 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,4 +6,11 @@ flag {
namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
+}
+
+flag {
+ name: "launch_selected_card_from_qs_tile"
+ namespace: "wallet_integration"
+ description: "When the wallet QS tile is tapped, launch the selected card pending intent instead of the home screen pending intent."
+ bug: "378469025"
} \ No newline at end of file
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index ded1a9949ef8..1e7bfe32ba79 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -36,6 +36,8 @@ using ::android::base::unique_fd;
namespace android {
+static constexpr bool kLogWeakReachableDeletedAssets = false;
+
static struct overlayableinfo_offsets_t {
jclass classObject;
jmethodID constructor;
@@ -97,7 +99,7 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
if (useCount > 1) {
ALOGW("ApkAssets: Deleting an object '%s' with %d > 1 strong and %d weak references",
(*assets)->GetDebugName().c_str(), int(useCount), int(weakCount));
- } else if (weakCount > 0) {
+ } else if constexpr (kLogWeakReachableDeletedAssets) if (weakCount > 0) {
ALOGW("ApkAssets: Deleting an ApkAssets object '%s' with %d weak references",
(*assets)->GetDebugName().c_str(), int(weakCount));
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a13e334e28c8..d8308aecfdc8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -9351,6 +9351,17 @@
</intent-filter>
</service>
+ <service android:name="com.android.ecm.EnhancedConfirmationCallTrackerService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:featureFlag="android.permission.flags.enhanced_confirmation_in_call_apis_enabled"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 565d584875ac..57959361bd48 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<!-- extends FrameLayout -->
+<!-- extends RelativeLayout -->
<NotificationHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_header"
@@ -62,7 +62,7 @@
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_toStartOf="@id/notification_buttons_column"
+ android:layout_toStartOf="@id/expand_button"
android:layout_alignWithParentIfMissing="true"
android:clipChildren="false"
android:gravity="center_vertical"
@@ -83,28 +83,17 @@
android:focusable="false"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
- >
-
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- />
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true" />
- </LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 29f14a4a93fc..227f84bb2eed 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -157,39 +157,27 @@
android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
+ android:minWidth="@dimen/notification_content_margin_end"
>
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:minWidth="@dimen/notification_content_margin_end"
- >
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
- </LinearLayout>
+ </FrameLayout>
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 6e9d17fc31d4..5459fa895f82 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
+<!-- extends FrameLayout -->
<com.android.internal.widget.MediaNotificationView
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
@@ -191,4 +192,11 @@
</FrameLayout>
</LinearLayout>
+
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 1eae41da1e81..2b3b7d873f4f 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -195,6 +195,12 @@
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
<LinearLayout
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 94ffb48e28c1..fc24f4599b25 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5111,7 +5111,6 @@
<java-symbol type="layout" name="notification_expand_button"/>
<java-symbol type="id" name="close_button" />
<java-symbol type="layout" name="notification_close_button"/>
- <java-symbol type="id" name="notification_buttons_column" />
<java-symbol type="bool" name="config_supportsMicToggle" />
<java-symbol type="bool" name="config_supportsCamToggle" />
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 031ed73e18ea..3ed9b7667be7 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -58,6 +58,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getActiveNfceeList();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public long getMaxPausePollingTimeoutMills();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public android.nfc.T4tNdefNfcee getT4tNdefNfcee();
@@ -66,7 +67,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int);
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int pausePolling(int);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int pausePolling(long);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int resumePolling();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 5ae1be201088..ac0a5aaaa195 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -53,7 +53,7 @@ interface INfcAdapter
int getState();
boolean disable(boolean saveState, in String pkg);
boolean enable(in String pkg);
- int pausePolling(int timeoutInMs);
+ int pausePolling(long timeoutInMs);
int resumePolling();
void setForegroundDispatch(in PendingIntent intent,
@@ -124,4 +124,5 @@ interface INfcAdapter
int commitRouting();
boolean isTagIntentAllowed(in String pkg, in int Userid);
IT4tNdefNfcee getT4tNdefNfceeInterface();
+ long getMaxPausePollingTimeoutMs();
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index f1198ed92cff..f78161e7cad0 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -748,15 +748,16 @@ public final class NfcOemExtension {
/**
* Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
* In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely.
- * Use {@link #resumePolling() to resume the polling.
- * @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
+ * Use {@link #resumePolling()} to resume the polling.
+ * Use {@link #getMaxPausePollingTimeoutMs()} to check the max timeout value.
+ * @param timeoutInMs the pause polling duration in millisecond.
* @return status of the operation
* @throws IllegalArgumentException if timeoutInMs value is invalid
* (0 < timeoutInMs < max).
*/
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong int timeoutInMs) {
+ public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong long timeoutInMs) {
return NfcAdapter.callServiceReturn(() ->
NfcAdapter.sService.pausePolling(timeoutInMs),
POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE);
@@ -776,6 +777,18 @@ public final class NfcOemExtension {
}
/**
+ * Gets the max pause polling timeout value in millisecond.
+ * @return long integer representing the max timeout
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @DurationMillisLong
+ public long getMaxPausePollingTimeoutMills() {
+ return NfcAdapter.callServiceReturn(() ->
+ NfcAdapter.sService.getMaxPausePollingTimeoutMs(), 0L);
+ }
+
+ /**
* Set whether to enable auto routing change or not (enabled by default).
* If disabled, routing targets are limited to a single off-host destination.
*
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 90e1808b7d44..39b0302beff8 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -38,8 +38,10 @@ android_app {
"StatementServiceParser",
"androidx.appcompat_appcompat",
"androidx.collection_collection-ktx",
+ "androidx.room_room-runtime",
"androidx.work_work-runtime",
"androidx.work_work-runtime-ktx",
"kotlinx-coroutines-android",
],
+ plugins: ["androidx.room_room-compiler-plugin"],
}
diff --git a/packages/StatementService/src/com/android/statementservice/database/Converters.kt b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
new file mode 100644
index 000000000000..21ecc8b4a651
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.statementservice.database
+
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilterGroup
+import android.util.JsonReader
+import androidx.room.TypeConverter
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.StringReader
+import java.util.ArrayList
+
+class Converters {
+ companion object {
+ private const val ACTION_NAME = "action"
+ private const val FILTERS_NAME = "filters"
+ private const val URI_PART_NAME = "uriPart"
+ private const val PATTERN_TYPE_NAME = "patternType"
+ private const val FILTER_NAME = "filter"
+ }
+
+ @TypeConverter
+ fun groupsToJson(groups: List<UriRelativeFilterGroup>): String {
+ val json = JSONArray()
+ for (group in groups) {
+ json.put(groupToJson(group))
+ }
+ return json.toString()
+ }
+
+ @TypeConverter
+ fun stringToGroups(json: String): List<UriRelativeFilterGroup> {
+ val groups = ArrayList<UriRelativeFilterGroup>()
+ StringReader(json).use { stringReader ->
+ JsonReader(stringReader).use { reader ->
+ reader.beginArray()
+ while (reader.hasNext()) {
+ groups.add(parseGroup(reader))
+ }
+ reader.endArray()
+ }
+ }
+ return groups
+ }
+
+ private fun groupToJson(group: UriRelativeFilterGroup): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(ACTION_NAME, group.action)
+ val filters = JSONArray()
+ for (filter in group.uriRelativeFilters) {
+ filters.put(filterToJson(filter))
+ }
+ jsonObject.put(FILTERS_NAME, filters)
+ return jsonObject
+ }
+
+ private fun filterToJson(filter: UriRelativeFilter): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(URI_PART_NAME, filter.uriPart)
+ jsonObject.put(PATTERN_TYPE_NAME, filter.patternType)
+ jsonObject.put(FILTER_NAME, filter.filter)
+ return jsonObject
+ }
+
+ private fun parseGroup(reader: JsonReader): UriRelativeFilterGroup {
+ val jsonObject = JSONObject()
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ ACTION_NAME -> jsonObject.put(ACTION_NAME, reader.nextInt())
+ FILTERS_NAME -> jsonObject.put(FILTERS_NAME, parseFilters(reader))
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+
+ val group = UriRelativeFilterGroup(jsonObject.getInt(ACTION_NAME))
+ val filters = jsonObject.getJSONArray(FILTERS_NAME)
+ for (i in 0 until filters.length()) {
+ val filter = filters.getJSONObject(i)
+ group.addUriRelativeFilter(UriRelativeFilter(
+ filter.getInt(URI_PART_NAME),
+ filter.getInt(PATTERN_TYPE_NAME),
+ filter.getString(FILTER_NAME)
+ ))
+ }
+ return group
+ }
+
+ private fun parseFilters(reader: JsonReader): JSONArray {
+ val filters = JSONArray()
+ reader.beginArray()
+ while (reader.hasNext()) {
+ filters.put(parseFilter(reader))
+ }
+ reader.endArray()
+ return filters
+ }
+
+ private fun parseFilter(reader: JsonReader): JSONObject {
+ reader.beginObject()
+ val jsonObject = JSONObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ URI_PART_NAME, PATTERN_TYPE_NAME -> jsonObject.put(name, reader.nextInt())
+ FILTER_NAME -> jsonObject.put(name, reader.nextString())
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+ return jsonObject
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
new file mode 100644
index 000000000000..c61666910cb4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import android.content.UriRelativeFilterGroup
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["packageName", "domain"])
+data class DomainGroups(
+ val packageName: String,
+ val domain: String,
+ val groups: List<UriRelativeFilterGroup>
+) \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
new file mode 100644
index 000000000000..3b4dcea48180
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface DomainGroupsDao {
+ @Query("SELECT * FROM DomainGroups WHERE packageName = :packageName")
+ fun getDomainGroups(packageName: String): List<DomainGroups>
+
+ @Insert
+ fun insertDomainGroups(vararg domainGroups: DomainGroups)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName AND domain = :domain")
+ fun clear(packageName: String, domain: String)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName")
+ fun clear(packageName: String)
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
new file mode 100644
index 000000000000..39833f6bc80b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.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.statementservice.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+
+@Database(entities = [DomainGroups::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class DomainGroupsDatabase : RoomDatabase() {
+ companion object {
+ private const val DATABASE_NAME = "domain-groups"
+ @Volatile
+ private var instance: DomainGroupsDatabase? = null
+
+ fun getInstance(context: Context) = instance ?: synchronized(this) {
+ instance ?: Room.databaseBuilder(
+ context,
+ DomainGroupsDatabase::class.java, DATABASE_NAME
+ ).build().also { instance = it }
+ }
+ }
+ abstract fun domainGroupsDao(): DomainGroupsDao
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
index acb54f6093de..0d7a1fdbcfb8 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.android.statementservice.domain.worker.CollectV1Worker
+import com.android.statementservice.domain.worker.GroupUpdateV1Worker
import com.android.statementservice.domain.worker.SingleV1RequestWorker
/**
@@ -67,7 +68,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
}
}
- //clear sp before enqueue unique work since policy is REPLACE
+ // clear sp before enqueue unique work since policy is REPLACE
val deContext = context.createDeviceProtectedStorageContext()
val editor = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)?.edit()
editor?.clear()?.apply()
@@ -78,6 +79,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
workRequests
)
.then(CollectV1Worker.buildRequest(verificationId, packageName))
+ .then(GroupUpdateV1Worker.buildRequest(packageName))
.enqueue()
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 29f844fb1a5d..6914347544de 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -24,6 +24,7 @@ import androidx.collection.LruCache
import com.android.statementservice.network.retriever.StatementRetriever
import com.android.statementservice.retriever.AbstractAsset
import com.android.statementservice.retriever.AbstractAssetMatcher
+import com.android.statementservice.retriever.Statement
import com.android.statementservice.utils.Result
import com.android.statementservice.utils.StatementUtils
import com.android.statementservice.utils.component1
@@ -87,10 +88,10 @@ class DomainVerifier private constructor(
host: String,
packageName: String,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] }
.takeIf { it!!.isPresent }
- ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER
+ ?: return Triple(WorkResult.failure(), VerifyStatus.FAILURE_PACKAGE_MANAGER, null)
return verifyHost(host, assetMatcher.get(), network)
}
@@ -98,34 +99,34 @@ class DomainVerifier private constructor(
host: String,
assetMatcher: AbstractAssetMatcher,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
var exception: Exception? = null
val resultAndStatus = try {
val sourceAsset = StatementUtils.createWebAssetString(host)
.let(AbstractAsset::create)
val result = retriever.retrieve(sourceAsset, network)
- ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN
+ ?: return Triple(WorkResult.success(), VerifyStatus.FAILURE_UNKNOWN, null)
when (result.responseCode) {
HttpURLConnection.HTTP_MOVED_PERM,
HttpURLConnection.HTTP_MOVED_TEMP -> {
- WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REDIRECT, null)
}
else -> {
- val isVerified = result.statements.any { statement ->
+ val statement = result.statements.firstOrNull { statement ->
(StatementUtils.RELATION.matches(statement.relation) &&
assetMatcher.matches(statement.target))
}
- if (isVerified) {
- WorkResult.success() to VerifyStatus.SUCCESS
+ if (statement != null) {
+ Triple(WorkResult.success(), VerifyStatus.SUCCESS, statement)
} else {
- WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REJECTED_BY_SERVER, statement)
}
}
}
} catch (e: Exception) {
exception = e
- WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN
+ Triple(WorkResult.retry(), VerifyStatus.FAILURE_UNKNOWN, null)
}
if (DEBUG) {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
index a17f9c9186ff..64d2d98b931b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
@@ -17,9 +17,12 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroupsDatabase
import com.android.statementservice.domain.DomainVerifier
abstract class BaseRequestWorker(
@@ -27,8 +30,19 @@ abstract class BaseRequestWorker(
protected val params: WorkerParameters
) : CoroutineWorker(appContext, params) {
+ protected val database = DomainGroupsDatabase.getInstance(appContext).domainGroupsDao()
+
protected val verificationManager =
appContext.getSystemService(DomainVerificationManager::class.java)!!
protected val verifier = DomainVerifier.getInstance(appContext)
+
+ protected fun updateUriRelativeFilterGroups(packageName: String, domainGroupUpdates: Map<String, List<UriRelativeFilterGroup>>) {
+ val verifiedDomains = verificationManager.getDomainVerificationInfo(packageName)?.hostToStateMap?.filterValues {
+ it == DomainVerificationInfo.STATE_SUCCESS || it == DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
+ }?.keys?.toList() ?: emptyList()
+ val domainGroups = verificationManager.getUriRelativeFilterGroups(packageName, verifiedDomains)
+ domainGroupUpdates.forEach { (domain, groups) -> domainGroups[domain] = groups }
+ verificationManager.setUriRelativeFilterGroups(packageName, domainGroups)
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
new file mode 100644
index 000000000000..f53dfc47acaa
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.coroutineScope
+
+class GroupUpdateV1Worker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+
+ private const val PACKAGE_NAME_KEY = "packageName"
+
+ fun buildRequest(packageName: String) = OneTimeWorkRequestBuilder<GroupUpdateV1Worker>()
+ .setInputData(
+ Data.Builder()
+ .putString(PACKAGE_NAME_KEY, packageName)
+ .build()
+ )
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+ updateUriRelativeFilterGroups(packageName)
+ Result.success()
+ }
+
+ private fun updateUriRelativeFilterGroups(packageName: String) {
+ val groupUpdates = database.getDomainGroups(packageName)
+ updateUriRelativeFilterGroups(
+ packageName,
+ groupUpdates.associateBy({it.domain}, {it.groups})
+ )
+ database.clear(packageName)
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
index 61ab2c264e6a..f83601a7807b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -17,10 +17,13 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.NetworkType
import androidx.work.WorkerParameters
import com.android.statementservice.domain.VerifyStatus
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -36,7 +39,13 @@ class RetryRequestWorker(
params: WorkerParameters
) : BaseRequestWorker(appContext, params) {
- data class VerifyResult(val domainSetId: UUID, val host: String, val status: VerifyStatus)
+ data class VerifyResult(
+ val domainSetId: UUID,
+ val host: String,
+ val status: VerifyStatus,
+ val packageName: String,
+ val groups: List<UriRelativeFilterGroup>
+ )
override suspend fun doWork() = coroutineScope {
if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
@@ -49,8 +58,11 @@ class RetryRequestWorker(
.map { (domainSetId, packageName, host) ->
async {
if (isActive && !isStopped) {
- val (_, status) = verifier.verifyHost(host, packageName, params.network)
- VerifyResult(domainSetId, host, status)
+ val (_, status, statement) = verifier.verifyHost(host, packageName, params.network)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ VerifyResult(domainSetId, host, status, packageName, groups)
} else {
// If the job gets cancelled, stop the remaining hosts, but continue the
// job to commit the results for hosts that were already requested.
@@ -60,17 +72,25 @@ class RetryRequestWorker(
}
.awaitAll()
.filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
- .groupBy { it.domainSetId }
- .forEach { (domainSetId, resultsById) ->
- resultsById.groupBy { it.status }
- .mapValues { it.value.map(VerifyResult::host).toSet() }
- .forEach { (status, hosts) ->
- verificationManager.setDomainVerificationStatus(
- domainSetId,
- hosts,
- status.value
- )
+ .groupBy { it.packageName }
+ .forEach { (packageName, resultsByName) ->
+ val groupUpdates = mutableMapOf<String, List<UriRelativeFilterGroup>>()
+ resultsByName.groupBy { it.domainSetId }
+ .forEach { (domainSetId, resultsById) ->
+ resultsById.groupBy { it.status }
+ .forEach { (status, verifyResults) ->
+ val error = verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ verifyResults.map(VerifyResult::host).toSet(),
+ status.value
+ )
+ if (error == DomainVerificationManager.STATUS_OK
+ && status == VerifyStatus.SUCCESS) {
+ verifyResults.forEach { groupUpdates[it.host] = it.groups }
+ }
+ }
}
+ updateUriRelativeFilterGroups(packageName, groupUpdates)
}
// Succeed regardless of results since this retry is best effort and not required
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
index 7a198cb59ca4..253a162a73a2 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -22,7 +22,9 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroups
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
@@ -60,7 +62,9 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ database.clear(packageName, host)
+
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
if (DEBUG) {
Log.d(
@@ -75,6 +79,10 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val deContext = appContext.createDeviceProtectedStorageContext()
val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
sp?.edit()?.putInt("$HOST_SUCCESS_PREFIX$host", status.value)?.apply()
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ database.insertDomainGroups(DomainGroups(packageName, host, groups))
Result.success()
}
is Result.Failure -> {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
index 562b132d36d6..8b1347a69932 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
@@ -22,6 +22,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
import java.util.UUID
@@ -59,9 +60,13 @@ class SingleV2RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
verificationManager.setDomainVerificationStatus(domainSetId, setOf(host), status.value)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ updateUriRelativeFilterGroups(packageName, mapOf(host to groups))
result
}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index ad137400fa86..d10cb0f91c11 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -39,6 +39,11 @@ object StatementParser {
private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
+ private const val COMMENTS_NAME = "comments"
+ private const val EXCLUDE_NAME = "exclude"
+ private const val FRAGMENT_NAME = "#"
+ private const val QUERY_NAME = "?"
+ private const val PATH_NAME = "/"
/**
* Parses a JSON array of statements.
@@ -99,9 +104,7 @@ object StatementParser {
FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
)
val target = AssetFactory.create(targetObject)
- val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
- statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
- )
+ val dynamicAppLinkComponents = parseDynamicAppLinkComponents(statement)
val statements = (0 until relations.length())
.map { relations.getString(it) }
@@ -129,13 +132,13 @@ object StatementParser {
}
private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
- val query = component.optJSONObject("?")
+ val query = component.optJSONObject(QUERY_NAME)
return DynamicAppLinkComponent.create(
- component.optBoolean("exclude", false),
- component.optString("#"),
- component.optString("/"),
+ component.optBoolean(EXCLUDE_NAME, false),
+ if (component.has(FRAGMENT_NAME)) component.getString(FRAGMENT_NAME) else null,
+ if (component.has(PATH_NAME)) component.getString(PATH_NAME) else null,
query?.keys()?.asSequence()?.associateWith { query.getString(it) },
- component.optString("comments")
+ component.optString(COMMENTS_NAME)
)
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
index dc27e125e204..c32f1949fed4 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -130,7 +130,7 @@ public final class DynamicAppLinkComponent {
@Override
public String toString() {
StringBuilder statement = new StringBuilder();
- statement.append("HandleAllUriRule: ");
+ statement.append("DynamicAppLinkComponent: ");
statement.append(mExclude);
statement.append(", ");
statement.append(mFragment);
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index 7635e8234dc0..ab1853c1d3ae 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -24,8 +24,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
/**
* A helper class that creates a {@link JSONObject} from a {@link JsonReader}.
@@ -48,7 +46,7 @@ public final class JsonParser {
JsonToken token = reader.peek();
if (token.equals(JsonToken.BEGIN_ARRAY)) {
- output.put(fieldName, new JSONArray(parseArray(reader)));
+ output.put(fieldName, parseArray(reader));
} else if (token.equals(JsonToken.STRING)) {
output.put(fieldName, reader.nextString());
} else if (token.equals(JsonToken.BEGIN_OBJECT)) {
@@ -57,9 +55,11 @@ public final class JsonParser {
} catch (JSONException e) {
errorMsg = e.getMessage();
}
+ } else if (token.equals(JsonToken.BOOLEAN)) {
+ output.put(fieldName, reader.nextBoolean());
} else {
reader.skipValue();
- errorMsg = "Unsupported value type.";
+ errorMsg = "Unsupported value type: " + token;
}
}
reader.endObject();
@@ -72,17 +72,36 @@ public final class JsonParser {
}
/**
- * Parses one string array from the {@link JsonReader}.
+ * Parses one JSON array from the {@link JsonReader}.
*/
- public static List<String> parseArray(JsonReader reader) throws IOException {
- ArrayList<String> output = new ArrayList<>();
+ public static JSONArray parseArray(JsonReader reader) throws IOException, JSONException {
+ JSONArray output = new JSONArray();
+ String errorMsg = null;
reader.beginArray();
while (reader.hasNext()) {
- output.add(reader.nextString());
+ JsonToken token = reader.peek();
+ if (token.equals(JsonToken.BEGIN_ARRAY)) {
+ output.put(parseArray(reader));
+ } else if (token.equals(JsonToken.STRING)) {
+ output.put(reader.nextString());
+ } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
+ try {
+ output.put(parse(reader));
+ } catch (JSONException e) {
+ errorMsg = e.getMessage();
+ }
+ } else {
+ reader.skipValue();
+ errorMsg = "Unsupported value type: " + token;
+ }
}
reader.endArray();
+ if (errorMsg != null) {
+ throw new JSONException(errorMsg);
+ }
+
return output;
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index 82bcecef1f70..55b87db232e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -27,6 +27,7 @@ import com.android.compose.animation.scene.UserActionResult
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.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -72,8 +73,10 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun actions_singleShade() =
+ fun actions_communalNotAvailable_singleShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -85,6 +88,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -102,12 +107,16 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
}
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun actions_splitShade() =
+ fun actions_communalNotAvailable_splitShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -119,6 +128,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -136,12 +147,136 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
}
@Test
@EnableFlags(DualShade.FLAG_NAME)
- fun actions_dualShade() =
+ fun actions_communalNotAvailable_dualShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Dual,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Dual,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual)
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_singleShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = true,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_splitShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = true,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_dualShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -155,6 +290,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
.isEqualTo(
UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
)
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -170,6 +307,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
.isEqualTo(
UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
)
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
}
private fun TestScope.setUpState(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
index b37206a4fef7..160574fa2244 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.ui.viewmodel
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.map
class DreamUserActionsViewModel
@AssistedInject
constructor(
+ private val communalInteractor: CommunalInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val shadeInteractor: ShadeInteractor,
) : UserActionsViewModel() {
@@ -50,10 +52,13 @@ constructor(
} else {
combine(
deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked },
+ communalInteractor.isCommunalAvailable,
shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, shadeMode ->
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
buildList {
- add(Swipe.Start to Scenes.Communal)
+ if (isCommunalAvailable) {
+ add(Swipe.Start to Scenes.Communal)
+ }
val bouncerOrGone =
if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8622ffc04601..160380bb09bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
import android.view.View
+import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -47,11 +48,15 @@ constructor(
visibility = View.GONE
}
}
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
}
-
+ if (emptyView.parent != null) {
+ // As emptyView is lazy, it might be already attached.
+ (emptyView.parent as? ViewGroup)?.removeView(emptyView)
+ }
constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 80ac2fcd4aa4..0a4e8c660761 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,6 +18,7 @@ package com.android.systemui.navigationbar.gestural;
import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHPAD;
+import static android.view.MotionEvent.TOOL_TYPE_FINGER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
@@ -216,6 +217,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final int mDisplayId;
private final UiThreadContext mUiThreadContext;
+ private final Handler mBgHandler;
private final Executor mBackgroundExecutor;
private final Rect mPipExcludedBounds = new Rect();
@@ -378,11 +380,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@Override
public void onInputDeviceAdded(int deviceId) {
if (isTrackpadDevice(deviceId)) {
- boolean wasEmpty = mTrackpadsConnected.isEmpty();
- mTrackpadsConnected.add(deviceId);
- if (wasEmpty) {
- update();
- }
+ // This updates the gesture handler state and should be running on the main thread.
+ mUiThreadContext.getHandler().post(() -> {
+ boolean wasEmpty = mTrackpadsConnected.isEmpty();
+ mTrackpadsConnected.add(deviceId);
+ if (wasEmpty) {
+ update();
+ }
+ });
}
}
@@ -391,10 +396,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@Override
public void onInputDeviceRemoved(int deviceId) {
- mTrackpadsConnected.remove(deviceId);
- if (mTrackpadsConnected.isEmpty()) {
- update();
- }
+ // This updates the gesture handler state and should be running on the main thread.
+ mUiThreadContext.getHandler().post(() -> {
+ mTrackpadsConnected.remove(deviceId);
+ if (mTrackpadsConnected.isEmpty()) {
+ update();
+ }
+ });
}
private void update() {
@@ -408,12 +416,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private boolean isTrackpadDevice(int deviceId) {
+ // This is a blocking binder call that should run on a bg thread.
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
if (inputDevice == null) {
return false;
}
- return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
- | InputDevice.SOURCE_TOUCHPAD);
+ return inputDevice.getSources() == (SOURCE_MOUSE | SOURCE_TOUCHPAD);
}
};
@@ -457,6 +465,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
mBackgroundExecutor = backgroundExecutor;
+ mBgHandler = bgHandler;
mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
@@ -611,9 +620,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- mInputManager.registerInputDeviceListener(
- mInputDeviceListener,
- mUiThreadContext.getHandler());
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mBgHandler);
int[] inputDevices = mInputManager.getInputDeviceIds();
for (int inputDeviceId : inputDevices) {
mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
@@ -1089,8 +1096,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
&& isValidTrackpadBackGesture(true /* isTrackpadEvent */);
} else {
mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
- && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
- && !isButtonPressFromTrackpad(ev);
+ && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+ && !isButtonPressFromTrackpad(ev);
}
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
@@ -1202,10 +1209,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private boolean isButtonPressFromTrackpad(MotionEvent ev) {
- // We don't allow back for button press from the trackpad, and yet we do with a mouse.
- int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
- int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
- return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
+ return ev.getSource() == (SOURCE_MOUSE | SOURCE_TOUCHPAD)
+ && ev.getToolType(ev.getActionIndex()) == TOOL_TYPE_FINGER;
}
private void dispatchToBackAnimation(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index fb62f8000c7c..0480212e4d59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -36,6 +36,7 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
@@ -108,6 +109,7 @@ import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderVi
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -125,6 +127,7 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
import com.android.systemui.wmshell.BubblesManager;
import java.io.PrintWriter;
@@ -430,6 +433,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private float mBottomRoundnessDuringLaunchAnimation;
private float mSmallRoundness;
+ private ListenerSet<DismissButtonTargetVisibilityListener>
+ mDismissButtonTargetVisibilityListeners
+ = new ListenerSet();
+
public NotificationContentView[] getLayouts() {
return Arrays.copyOf(mLayouts, mLayouts.length);
}
@@ -739,6 +746,73 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ public interface DismissButtonTargetVisibilityListener {
+ // Called when the notification dismiss button's target visibility changes.
+ // NOTE: This can be called when the dismiss button already has the target visibility.
+ void onTargetVisibilityChanged(boolean targetVisible);
+ }
+
+ public void addDismissButtonTargetStateListener(
+ DismissButtonTargetVisibilityListener listener) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ mDismissButtonTargetVisibilityListeners.addIfAbsent(listener);
+ }
+
+ public void removeDismissButtonTargetStateListener(
+ DismissButtonTargetVisibilityListener listener) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ mDismissButtonTargetVisibilityListeners.remove(listener);
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+ return super.onInterceptHoverEvent(event);
+ }
+
+ // Do not bother checking the dismiss button's target visibility if the notification cannot
+ // be dismissed.
+ if (!canEntryBeDismissed()) {
+ return false;
+ }
+
+ final Boolean targetVisible = getDismissButtonTargetVisibilityIfAny(event);
+ if (targetVisible != null) {
+ for (DismissButtonTargetVisibilityListener listener :
+ mDismissButtonTargetVisibilityListeners) {
+ listener.onTargetVisibilityChanged(targetVisible.booleanValue());
+ }
+ }
+
+ // Do not consume the hover event so that children still have a chance to process it.
+ return false;
+ }
+
+ private @Nullable Boolean getDismissButtonTargetVisibilityIfAny(MotionEvent event) {
+ // Returns the dismiss button's target visibility resulted by `event`. Returns null if the
+ // target visibility should not change.
+
+ if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ // The notification dismiss button should be hidden when the hover exit event is located
+ // outside of the notification. NOTE: The hover exit event can be inside the
+ // notification if hover moves from one hoverable child to another.
+ final Rect localBounds = new Rect(0, 0, this.getWidth(), this.getActualHeight());
+ if (!localBounds.contains((int) event.getX(), (int) event.getY())) {
+ return Boolean.FALSE;
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ return Boolean.TRUE;
+ }
+
+ return null;
+ }
+
private void updateLimitsForView(NotificationContentView layout) {
View contractedView = layout.getContractedChild();
boolean customView = contractedView != null
@@ -2218,6 +2292,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.remove(mGutsStub);
// We don't handle focus highlight in this view, it's done in background drawable instead
setDefaultFocusHighlightEnabled(false);
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ addDismissButtonTargetStateListener(findViewById(R.id.backgroundNormal));
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index d0db5145e0ff..34ef63944f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -21,7 +21,9 @@ import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
+import android.graphics.Path;
import android.graphics.PorterDuff;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
@@ -36,6 +38,7 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.util.DrawableDumpKt;
import java.io.PrintWriter;
@@ -44,7 +47,8 @@ import java.util.Arrays;
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
-public class NotificationBackgroundView extends View implements Dumpable {
+public class NotificationBackgroundView extends View implements Dumpable,
+ ExpandableNotificationRow.DismissButtonTargetVisibilityListener {
private final boolean mDontModifyCorners;
private Drawable mBackground;
@@ -66,6 +70,11 @@ public class NotificationBackgroundView extends View implements Dumpable {
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
private final int mNormalColor;
+ private final int convexR = 9;
+ private final int concaveR = 22;
+
+ // True only if the dismiss button is visible.
+ private boolean mDrawDismissButtonCutout = false;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -80,6 +89,18 @@ public class NotificationBackgroundView extends View implements Dumpable {
}
@Override
+ public void onTargetVisibilityChanged(boolean targetVisible) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ if (mDrawDismissButtonCutout != targetVisible) {
+ mDrawDismissButtonCutout = targetVisible;
+ invalidate();
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
canvas.save();
@@ -87,12 +108,87 @@ public class NotificationBackgroundView extends View implements Dumpable {
canvas.clipRect(0, mClipTopAmount, getWidth(),
getActualHeight() - mClipBottomAmount);
}
- draw(canvas, mBackground);
+
+ if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+ draw(canvas, mBackground);
+ canvas.restore();
+ return;
+ }
+
+ Rect backgroundBounds = null;
+ if (mBackground != null || mDrawDismissButtonCutout) {
+ backgroundBounds = calculateBackgroundBounds();
+ }
+
+ if (mDrawDismissButtonCutout) {
+ canvas.clipPath(calculateDismissButtonCutoutPath(backgroundBounds));
+ }
+
+ if (mBackground != null) {
+ mBackground.setBounds(backgroundBounds);
+ mBackground.draw(canvas);
+ }
+
canvas.restore();
}
}
+ private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
+ // TODO(b/365585705): Adapt to RTL after the UX design is finalized.
+
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ Path path = new Path();
+
+ final int left = backgroundBounds.left;
+ final int right = backgroundBounds.right;
+ final int top = backgroundBounds.top;
+ final int bottom = backgroundBounds.bottom;
+
+ // Generate the path clockwise from the left-top corner.
+ path.moveTo(left, top);
+ path.lineTo(right - 2 * convexR - concaveR, top);
+ path.quadTo(right - convexR - concaveR, top, right - convexR - concaveR,
+ top + convexR);
+ path.quadTo(right - convexR - concaveR, top + convexR + concaveR, right - convexR,
+ top + convexR + concaveR);
+ path.quadTo(right, top + convexR + concaveR, right, top + 2 * convexR + concaveR);
+ path.lineTo(right, bottom);
+ path.lineTo(left, bottom);
+ path.lineTo(left, top);
+
+ return path;
+ }
+
+ private Rect calculateBackgroundBounds() {
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ int top = 0;
+ int bottom = getActualHeight();
+ if (mBottomIsRounded
+ && mBottomAmountClips
+ && !mExpandAnimationRunning) {
+ bottom -= mClipBottomAmount;
+ }
+ final boolean isRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int actualWidth = getActualWidth();
+
+ int left = isRtl ? width - actualWidth : 0;
+ int right = isRtl ? width : actualWidth;
+
+ if (mExpandAnimationRunning) {
+ // Horizontally center this background view inside of the container
+ left = (int) ((width - actualWidth) / 2.0f);
+ right = (int) (left + actualWidth);
+ }
+
+ return new Rect(left, top, right, bottom);
+ }
+
private void draw(Canvas canvas, Drawable drawable) {
+ NotificationAddXOnHoverToDismiss.assertInLegacyMode();
+
if (drawable != null) {
int top = 0;
int bottom = getActualHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index b622defbef98..e9eecdd8a26f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.row.wrapper;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
import android.app.Notification;
@@ -48,6 +51,7 @@ import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import java.util.Stack;
@@ -115,6 +119,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
resolveHeaderViews();
addFeedbackOnClickListener(row);
addCloseButtonOnClickListener(row);
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ mRow.addDismissButtonTargetStateListener(mHoverListener);
+ }
}
@Override
@@ -166,13 +174,34 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
}
}
+ private ExpandableNotificationRow.DismissButtonTargetVisibilityListener mHoverListener = new
+ ExpandableNotificationRow.DismissButtonTargetVisibilityListener() {
+ @Override
+ public void onTargetVisibilityChanged(boolean targetVisible) {
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ if (mCloseButton != null) {
+ mCloseButton.setVisibility(targetVisible ? VISIBLE : GONE);
+ }
+ }
+ };
+
+ @Override
+ public void setRemoved() {
+ super.setRemoved();
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ mRow.removeDismissButtonTargetStateListener(mHoverListener);
+ }
+ }
+
/**
* Shows the given feedback icon, or hides the icon if null.
*/
@Override
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mFeedbackIcon != null) {
- mFeedbackIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+ mFeedbackIcon.setVisibility(icon != null ? VISIBLE : GONE);
if (icon != null) {
if (mFeedbackIcon instanceof ImageButton) {
((ImageButton) mFeedbackIcon).setImageResource(icon.getIconRes());
@@ -266,7 +295,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
boolean expandable,
View.OnClickListener onClickListener,
boolean requestLayout) {
- mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mExpandButton.setVisibility(expandable ? VISIBLE : GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mAltExpandTarget != null) {
mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
@@ -294,7 +323,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
@Override
public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
if (mAudiblyAlertedIcon != null) {
- mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
+ mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? VISIBLE : GONE);
}
}
@@ -371,6 +400,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
((DateTimeView) timeView).setTime(whenMillis);
}
}
+
protected void addTransformedViews(View... views) {
for (View view : views) {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt
new file mode 100644
index 000000000000..0961874b2276
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification dismiss button on hover flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAddXOnHoverToDismiss {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS
+
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAddXOnHoverToDismiss()
+
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index bf4ef509ac80..eb1b44b75247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -59,6 +59,7 @@ import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
@@ -242,10 +243,14 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+
mMediaSwitchingController.mInputRouteManager =
new InputRouteManager(mContext, mAudioManager);
mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager);
mMediaSwitchingController.mInputRouteManager = mInputRouteManager;
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+ .thenReturn(new AudioDeviceInfo[0]);
+
MediaDescription.Builder builder = new MediaDescription.Builder();
builder.setTitle(TEST_SONG);
builder.setSubtitle(TEST_ARTIST);
@@ -483,11 +488,11 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
verify(mMediaDevice2, never()).setRangeZone(anyInt());
}
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
// This test relies on mMediaSwitchingController.start being called while the selected
- // device
- // list has exactly one item, and that item's id is:
+ // device list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
// built-in speakers).
@@ -511,16 +516,54 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // There should be 2 non-MediaDevice items: the "Speakers & Display" title, and the "Connect
+ // a device" button.
assertThat(mMediaSwitchingController.getMediaItemList().size())
.isEqualTo(mMediaDevices.size() + 2);
verify(mCb).onDeviceListChanged();
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void onDeviceListUpdate_verifyDeviceListCallback_inputRouting() {
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices.containsAll(mMediaDevices)).isTrue();
+ assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // When input routing is enabled, there should be 4 non-MediaDevice items: one for
+ // the "Output" title, one for the "Speakers & Displays" title, one for the "Connect a
+ // device" button, and one for the "Input" title.
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 4);
+ verify(mCb).onDeviceListChanged();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
// This test relies on mMediaSwitchingController.start being called while the selected
- // device
- // list has exactly one item, and that item's id is:
+ // device list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
// built-in speakers).
@@ -547,6 +590,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // There should be 1 non-MediaDevice item: the "Speakers & Display" title.
assertThat(mMediaSwitchingController.getMediaItemList().size())
.isEqualTo(mMediaDevices.size() + 1);
verify(mCb).onDeviceListChanged();
@@ -554,6 +598,45 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
+ public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize_inputRouting() {
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ when(mMediaDevice1.getFeatures())
+ .thenReturn(ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices.containsAll(mMediaDevices)).isTrue();
+ assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // When input routing is enabled, there should be 3 non-MediaDevice items: one for
+ // the "Output" title, one for the "Speakers & Displays" title, and one for the "Input"
+ // title.
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 3);
+ verify(mCb).onDeviceListChanged();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
public void onInputDeviceListUpdate_verifyDeviceListCallback() {
AudioDeviceInfo[] audioDeviceInfos = {};
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
index b24b3ad05117..71746b505a48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -23,6 +24,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.dreamUserActionsViewModel by
Kosmos.Fixture {
DreamUserActionsViewModel(
+ communalInteractor = communalInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 053ec824e1a7..d79e66db8661 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -131,9 +131,6 @@ import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.security.Flags.preventIntentRedirect;
-import static android.security.Flags.preventIntentRedirectCollectNestedKeysOnServerIfNotCollected;
-import static android.security.Flags.preventIntentRedirectShowToast;
-import static android.security.Flags.preventIntentRedirectThrowExceptionIfNestedKeysNotCollected;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -390,7 +387,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.autofill.AutofillManagerInternal;
-import android.widget.Toast;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
@@ -441,7 +437,6 @@ import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
-import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
@@ -483,7 +478,6 @@ import com.android.server.wm.WindowProcessController;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.VMRuntime;
-
import libcore.util.EmptyArray;
import java.io.File;
@@ -19319,31 +19313,8 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
if (!preventIntentRedirect()) return;
- if (intent == null) return;
- if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0) {
- Slog.wtf(TAG,
- "[IntentRedirect] The intent does not have its nested keys collected as a "
- + "preparation for creating intent creator tokens. Intent: "
- + intent + "; creatorPackage: " + creatorPackage);
- if (preventIntentRedirectShowToast()) {
- UiThread.getHandler().post(
- () -> Toast.makeText(mContext,
- "Nested keys not collected. go/report-bug-intentRedir to report a"
- + " bug", Toast.LENGTH_LONG).show());
- }
- if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
- // this flag will be internal only, not ramped to public.
- throw new SecurityException(
- "The intent does not have its nested keys collected as a preparation for "
- + "creating intent creator tokens. Intent: "
- + intent + "; creatorPackage: " + creatorPackage);
- }
- if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
- // this flag will be ramped to public.
- intent.collectExtraIntentKeys();
- }
- }
+ if (intent == null) return;
String targetPackage = intent.getComponent() != null
? intent.getComponent().getPackageName()
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index ab7cd5f29660..1a6051b6ac9c 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -66,6 +66,9 @@ per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNE
# Activity Security
per-file ActivityManager* = file:/ACTIVITY_SECURITY_OWNERS
+# Aconfig Flags
+per-file flags.aconfig = yamasani@google.com, bills@google.com, nalini@google.com
+
# Londoners
michaelwr@google.com #{LAST_RESORT_SUGGESTION}
narayan@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
index eed2bddb0312..958ab8ee0b6a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
@@ -18,6 +18,8 @@ package com.android.server.biometrics.sensors;
import android.util.SparseArray;
+import java.util.concurrent.ConcurrentHashMap;
+
/**
* Tracks biometric performance across sensors and users.
*/
@@ -25,17 +27,12 @@ public class PerformanceTracker {
private static final String TAG = "PerformanceTracker";
// Keyed by SensorId
- private static SparseArray<PerformanceTracker> sTrackers;
+ private static final ConcurrentHashMap<Integer, PerformanceTracker> sTrackers =
+ new ConcurrentHashMap<>();
public static PerformanceTracker getInstanceForSensorId(int sensorId) {
- if (sTrackers == null) {
- sTrackers = new SparseArray<>();
- }
-
- if (!sTrackers.contains(sensorId)) {
- sTrackers.put(sensorId, new PerformanceTracker());
- }
- return sTrackers.get(sensorId);
+ PerformanceTracker tracker = sTrackers.putIfAbsent(sensorId, new PerformanceTracker());
+ return tracker != null ? tracker : sTrackers.get(sensorId);
}
private static class Info {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 36e4a7e6a7ef..944e85cc970e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -851,13 +851,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
for (int i = 0; i < mFaceSensors.size(); i++) {
final Sensor sensor = mFaceSensors.valueAt(i);
final int sensorId = mFaceSensors.keyAt(i);
- final PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(
- sensorId);
- if (performanceTracker != null) {
- performanceTracker.incrementHALDeathCount();
- } else {
- Slog.w(getTag(), "Performance tracker is null. Not counting HAL death.");
- }
+ PerformanceTracker.getInstanceForSensorId(sensorId)
+ .incrementHALDeathCount();
sensor.onBinderDied();
}
});
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0337f5f4f9c8..bbef5785dfcb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3181,7 +3181,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
}
int orientation = screenOrientations[i];
- if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+ if (orientation == ORIENTATION_UNKNOWN && crops.size() > 1) {
throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+ "screen orientation should only be used in a singleton map");
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 63f29f639204..a0c0b9836507 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10314,7 +10314,7 @@ public class WindowManagerService extends IWindowManager.Stub
mH.post(() -> {
Toast.makeText(mContext, Looper.getMainLooper(),
mContext.getString(R.string.screen_not_shared_sensitive_content),
- Toast.LENGTH_SHORT)
+ Toast.LENGTH_LONG)
.show();
});
// If blocked due to notification protection (null window token) log protection applied
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e307e529a40b..228e32e98cc7 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -70,6 +70,7 @@ public final class ProfcollectForwardingService extends SystemService {
private int mUsageSetting;
private boolean mUploadEnabled;
+ private static boolean sVerityEnforced;
private boolean mAdbActive;
private IProfCollectd mIProfcollect;
@@ -117,6 +118,13 @@ public final class ProfcollectForwardingService extends SystemService {
mUsageSetting = -1;
}
+ // Check verity, disable profile upload if not enforced.
+ final String verityMode = SystemProperties.get("ro.boot.veritymode");
+ sVerityEnforced = verityMode.equals("enforcing");
+ if (!sVerityEnforced) {
+ Log.d(LOG_TAG, "verity is not enforced: " + verityMode);
+ }
+
mUploadEnabled =
context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
@@ -373,6 +381,10 @@ public final class ProfcollectForwardingService extends SystemService {
Log.i(LOG_TAG, "Upload is not enabled.");
return;
}
+ if (!sVerityEnforced) {
+ Log.i(LOG_TAG, "Verity is not enforced.");
+ return;
+ }
Intent intent = new Intent()
.setPackage("com.android.shell")
.setAction("com.android.shell.action.PROFCOLLECT_UPLOAD")