diff options
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") |