summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/preloaded-classes1
-rw-r--r--config/preloaded-classes1
-rw-r--r--core/java/android/content/EventLogTags.logtags2
-rw-r--r--core/java/android/content/res/flags.aconfig8
-rw-r--r--core/java/android/content/res/loader/ResourcesProvider.java5
-rw-r--r--core/java/android/credentials/flags.aconfig10
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java9
-rw-r--r--core/java/android/os/EventLogTags.logtags2
-rw-r--r--core/java/android/os/UserManager.java6
-rw-r--r--core/java/android/permission/flags.aconfig2
-rw-r--r--core/java/android/view/EventLogTags.logtags4
-rw-r--r--core/java/android/widget/RemoteViews.java2
-rw-r--r--core/java/com/android/internal/app/EventLogTags.logtags2
-rw-r--r--core/java/com/android/internal/content/om/OverlayManagerImpl.java14
-rw-r--r--core/java/com/android/internal/logging/EventLogTags.logtags2
-rw-r--r--core/java/org/chromium/arc/EventLogTags.logtags2
-rw-r--r--core/tests/overlaytests/device_self_targeting/Android.bp1
-rw-r--r--core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java21
-rw-r--r--core/tests/overlaytests/host/Android.bp18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt144
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt12
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt23
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt9
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt7
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt8
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt5
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt44
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt10
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt2
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt6
-rw-r--r--packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt10
-rw-r--r--packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt20
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java33
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java23
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java16
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java9
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java38
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java55
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java31
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java13
52 files changed, 675 insertions, 196 deletions
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index a696e03d5bdf..afd9984cb124 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -6469,6 +6469,7 @@ android.os.connectivity.WifiActivityEnergyInfo$1
android.os.connectivity.WifiActivityEnergyInfo
android.os.connectivity.WifiBatteryStats$1
android.os.connectivity.WifiBatteryStats
+android.os.flagging.AconfigPackage
android.os.health.HealthKeys$Constant
android.os.health.HealthKeys$Constants
android.os.health.HealthKeys$SortedIntArray
diff --git a/config/preloaded-classes b/config/preloaded-classes
index ed402767ee64..343de0bf3b98 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6473,6 +6473,7 @@ android.os.connectivity.WifiActivityEnergyInfo$1
android.os.connectivity.WifiActivityEnergyInfo
android.os.connectivity.WifiBatteryStats$1
android.os.connectivity.WifiBatteryStats
+android.os.flagging.AconfigPackage
android.os.health.HealthKeys$Constant
android.os.health.HealthKeys$Constants
android.os.health.HealthKeys$SortedIntArray
diff --git a/core/java/android/content/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags
index 21ea90ad2e1e..861a5b72c86c 100644
--- a/core/java/android/content/EventLogTags.logtags
+++ b/core/java/android/content/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.content;
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 6fc7d90a8237..ecb4bb1394b6 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -114,3 +114,11 @@ flag {
bug: "373535266"
is_fixed_read_only: true
}
+
+flag {
+ name: "self_targeting_android_resource_frro"
+ is_exported: true
+ namespace: "customization_picker"
+ description: "Fixes bug in Launcher preview by enabling overlays targeting 'android'"
+ bug: "377545987"
+} \ No newline at end of file
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 830b7e0fa2d0..7eba1819d148 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -25,6 +25,7 @@ import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
+import android.content.res.Flags;
import android.os.ParcelFileDescriptor;
import android.util.Log;
@@ -90,6 +91,10 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
throws IOException {
Objects.requireNonNull(overlayInfo);
Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+ if (!Flags.selfTargetingAndroidResourceFrro()) {
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+ }
final String overlayName =
OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
final String path =
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d8d4e161006c..9c811fb84da3 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -124,3 +124,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "credential_manager"
+ name: "settings_w_fixes"
+ description: "Settings improvements for credential manager"
+ bug: "373711451"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 5f3c15d1842e..4c9e73c8b21f 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3202,7 +3202,8 @@ public class InputMethodService extends AbstractInputMethodService {
*/
@FlaggedApi(Flags.FLAG_ADAPTIVE_HANDWRITING_BOUNDS)
public final void setStylusHandwritingRegion(@NonNull Region handwritingRegion) {
- if (handwritingRegion.equals(mLastHandwritingRegion)) {
+ final Region immutableHandwritingRegion = new Region(handwritingRegion);
+ if (immutableHandwritingRegion.equals(mLastHandwritingRegion)) {
Log.v(TAG, "Failed to set setStylusHandwritingRegion():"
+ " same region set twice.");
return;
@@ -3210,10 +3211,10 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) {
Log.d(TAG, "Setting new handwriting region for stylus handwriting "
- + handwritingRegion + " from last " + mLastHandwritingRegion);
+ + immutableHandwritingRegion + " from last " + mLastHandwritingRegion);
}
- mPrivOps.setHandwritingTouchableRegion(handwritingRegion);
- mLastHandwritingRegion = handwritingRegion;
+ mPrivOps.setHandwritingTouchableRegion(immutableHandwritingRegion);
+ mLastHandwritingRegion = immutableHandwritingRegion;
}
/**
diff --git a/core/java/android/os/EventLogTags.logtags b/core/java/android/os/EventLogTags.logtags
index b143a7443066..f57aad00e591 100644
--- a/core/java/android/os/EventLogTags.logtags
+++ b/core/java/android/os/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.os
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7e73a5d04866..b9f2cfcd8ca8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6501,7 +6501,11 @@ public class UserManager {
* @hide
*/
public static final void invalidateCacheOnUserDataChanged() {
- if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+ if (android.multiuser.Flags.cacheProfilesReadOnly()
+ || android.multiuser.Flags.cacheUserInfoReadOnly()) {
+ // TODO(b/383175685): Rename the invalidation call to make it clearer that it
+ // invalidates the caches for both getProfiles and getUserInfo (since they both use the
+ // same user_manager_user_data CachedProperty.api).
UserManagerCache.invalidateProfiles();
}
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index aacc6e2a3156..af96ccfee787 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -57,7 +57,7 @@ flag {
is_exported: true
is_fixed_read_only: true
namespace: "permissions"
- description: "enable enhanced confirmation incall apis"
+ description: "DEPRECATED, does not gate any apis"
bug: "364535720"
}
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index f3792930647a..95894fa32d6b 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package android.view
@@ -35,7 +35,7 @@ option java_package android.view
# 6: Percent
# Default value for data of type int/long is 2 (bytes).
#
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
# 32000 - 32999 reserved for input method framework
# IME animation is started.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9c2833b91a2b..9fe3fd6ddc1a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,6 +20,7 @@ import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO;
import static android.appwidget.flags.Flags.drawDataParcel;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO;
import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -8698,6 +8699,7 @@ public class RemoteViews implements Parcelable, Filter {
*
* @hide
*/
+ @FlaggedApi(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO)
@Nullable
public static ColorResources createWithOverlay(Context context,
SparseIntArray colorMapping) {
diff --git a/core/java/com/android/internal/app/EventLogTags.logtags b/core/java/com/android/internal/app/EventLogTags.logtags
index d681a8d26e8e..a18a8243305b 100644
--- a/core/java/com/android/internal/app/EventLogTags.logtags
+++ b/core/java/com/android/internal/app/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.internal.app;
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index fa5cf2a396b9..5d4e6a083af4 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -36,6 +36,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.res.AssetManager;
+import android.content.res.Flags;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
@@ -235,17 +236,24 @@ public class OverlayManagerImpl {
Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
checkPackageName(overlayInternal.packageName);
- Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+ if (Flags.selfTargetingAndroidResourceFrro()) {
+ Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+ } else {
+ checkPackageName(overlayInternal.targetPackageName);
+ Preconditions.checkStringNotEmpty(
+ overlayInternal.targetOverlayable,
+ "Target overlayable should be neither null nor empty string.");
+ }
final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
String targetPackage = null;
- if (TextUtils.equals(overlayInternal.targetPackageName, "android")) {
+ if (Flags.selfTargetingAndroidResourceFrro() && TextUtils.equals(
+ overlayInternal.targetPackageName, "android")) {
targetPackage = AssetManager.FRAMEWORK_APK_PATH;
} else {
targetPackage = Preconditions.checkStringNotEmpty(
applicationInfo.getBaseCodePath());
}
-
final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 693bd16e6170..db47797cb03d 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.internal.logging;
diff --git a/core/java/org/chromium/arc/EventLogTags.logtags b/core/java/org/chromium/arc/EventLogTags.logtags
index 1b7160e90224..8102d6f10ed4 100644
--- a/core/java/org/chromium/arc/EventLogTags.logtags
+++ b/core/java/org/chromium/arc/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package org.chromium.arc
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
index 931eac515e31..14a3cdf98436 100644
--- a/core/tests/overlaytests/device_self_targeting/Android.bp
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -31,6 +31,7 @@ android_test {
"androidx.test.ext.junit",
"mockito-target-minus-junit4",
"truth",
+ "flag-junit",
],
optimize: {
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index 28d6545c8a5b..bcf1446b3467 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -16,6 +16,7 @@
package com.android.overlaytest;
+import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
@@ -41,6 +42,8 @@ import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
@@ -76,6 +79,8 @@ import java.util.List;
*/
@RunWith(AndroidJUnit4.class)
public class OverlayManagerImplTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String TAG = "OverlayManagerImplTest";
private static final String TARGET_COLOR_RES = "color/mycolor";
@@ -210,6 +215,22 @@ public class OverlayManagerImplTest {
}
@Test
+ @DisableFlags(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO)
+ public void registerOverlay_forAndroidPackage_shouldFail() {
+ FabricatedOverlayInternal overlayInternal =
+ createOverlayWithName(
+ mOverlayName,
+ SYSTEM_APP_OVERLAYABLE,
+ "android",
+ List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+ assertThrows(
+ "Wrong target package name",
+ IllegalArgumentException.class,
+ () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+ }
+
+ @Test
public void getOverlayInfosForTarget_defaultShouldBeZero() {
List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index 634098074cca..9b7200436f02 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -28,14 +28,14 @@ java_test_host {
test_suites: [
"device-tests",
],
- target_required: [
- "OverlayHostTests_NonPlatformSignatureOverlay",
- "OverlayHostTests_PlatformSignatureStaticOverlay",
- "OverlayHostTests_PlatformSignatureOverlay",
- "OverlayHostTests_UpdateOverlay",
- "OverlayHostTests_FrameworkOverlayV1",
- "OverlayHostTests_FrameworkOverlayV2",
- "OverlayHostTests_AppOverlayV1",
- "OverlayHostTests_AppOverlayV2",
+ device_common_data: [
+ ":OverlayHostTests_NonPlatformSignatureOverlay",
+ ":OverlayHostTests_PlatformSignatureStaticOverlay",
+ ":OverlayHostTests_PlatformSignatureOverlay",
+ ":OverlayHostTests_UpdateOverlay",
+ ":OverlayHostTests_FrameworkOverlayV1",
+ ":OverlayHostTests_FrameworkOverlayV2",
+ ":OverlayHostTests_AppOverlayV1",
+ ":OverlayHostTests_AppOverlayV2",
],
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
index 3fa8df40dfef..a92100410d3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
@@ -95,13 +95,16 @@ class StageOrderOperator (
*/
fun onEnteringSplit(@SnapPosition goingToLayout: Int) {
if (goingToLayout == currentLayout) {
- // Add protolog here. Return for now, but maybe we want to handle swap case, TBD
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Entering Split requested same layout split is in: %d", goingToLayout)
return
}
val freeStages: List<StageTaskListener> =
allStages.filterNot { activeStages.contains(it) }
when(goingToLayout) {
- SplitScreenConstants.SNAP_TO_2_50_50 -> {
+ SplitScreenConstants.SNAP_TO_2_50_50,
+ SplitScreenConstants.SNAP_TO_2_33_66,
+ SplitScreenConstants.SNAP_TO_2_66_33 -> {
if (activeStages.size < 2) {
// take from allStages and add into activeStages
for (i in 0 until (2 - activeStages.size)) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt
new file mode 100644
index 000000000000..3b4a86a71d90
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.wm.shell.splitscreen
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.IconProvider
+import com.android.wm.shell.Flags.enableFlexibleSplit
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33
+import com.android.wm.shell.splitscreen.StageTaskListener.StageListenerCallbacks
+import com.android.wm.shell.windowdecor.WindowDecorViewModel
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StageOrderOperatorTests : ShellTestCase() {
+
+ @Mock
+ lateinit var mTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ lateinit var mSyncQueue: SyncTransactionQueue
+ @Mock
+ lateinit var stageListenerCallbacks: StageListenerCallbacks
+ @Mock
+ lateinit var iconProvider: IconProvider
+ @Mock
+ lateinit var windowDecorViewModel: Optional<WindowDecorViewModel>
+
+ lateinit var stageOrderOperator: StageOrderOperator
+
+ @Before
+ fun setup() {
+ stageOrderOperator = StageOrderOperator(
+ context,
+ mTaskOrganizer,
+ DEFAULT_DISPLAY,
+ stageListenerCallbacks,
+ mSyncQueue,
+ iconProvider,
+ windowDecorViewModel,
+ )
+ assert(stageOrderOperator.activeStages.size == 0)
+ }
+
+ @Test
+ fun activeStages_2_2app_50_50_split() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_50_50)
+ assert(stageOrderOperator.activeStages.size == 2)
+ }
+
+ @Test
+ fun activeStages_2_2app_33_66_split() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_33_66)
+ assert(stageOrderOperator.activeStages.size == 2)
+ }
+
+ @Test
+ fun activeStages_2_2app_66_33_split() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ assert(stageOrderOperator.activeStages.size == 2)
+ }
+
+ @Test
+ fun activateSameCountStage_noOp() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ assert(stageOrderOperator.activeStages.size == 2)
+ }
+
+ @Test
+ fun deactivate_emptyActiveStages() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ stageOrderOperator.onExitingSplit()
+ assert(stageOrderOperator.activeStages.isEmpty())
+ }
+
+ @Test
+ fun swapDividerPos_twoApps() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+ val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+ stageOrderOperator.onDoubleTappedDivider()
+ val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+ val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+ assert(stageIndex0 == newStageIndex1)
+ assert(stageIndex1 == newStageIndex0)
+ }
+
+ @Test
+ fun swapDividerPos_twiceNoOp_twoApps() {
+ assumeTrue(enableFlexibleSplit())
+
+ stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+ val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+ val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+ stageOrderOperator.onDoubleTappedDivider()
+ stageOrderOperator.onDoubleTappedDivider()
+ val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+ val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+ assert(stageIndex0 == newStageIndex0)
+ assert(stageIndex1 == newStageIndex1)
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 1eff0eee606d..91ac34ac8233 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -72,7 +72,6 @@ import java.util.regex.Pattern;
public final class DeviceConfigService extends Binder {
private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
"/system/etc/aconfig_flags.pb",
- "/system_ext/etc/aconfig_flags.pb",
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 7aed61533aac..5cd534e62ea9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -171,7 +171,6 @@ final class SettingsState {
private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
"/system/etc/aconfig_flags.pb",
- "/system_ext/etc/aconfig_flags.pb",
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index ae7c44e9b146..8b9ae9a0606d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -39,7 +39,7 @@ class EditTileListStateTest : SysuiTestCase() {
@Test
fun startDrag_listHasSpacers() {
- underTest.onStarted(TestEditTiles[0])
+ underTest.onStarted(TestEditTiles[0], DragType.Add)
// [ a ] [ b ] [ c ] [ X ]
// [ Large D ] [ e ] [ X ]
@@ -51,8 +51,8 @@ class EditTileListStateTest : SysuiTestCase() {
@Test
fun moveDrag_listChanges() {
- underTest.onStarted(TestEditTiles[4])
- underTest.onMoved(3, false)
+ underTest.onStarted(TestEditTiles[4], DragType.Add)
+ underTest.onTargeting(3, false)
// Tile E goes to index 3
// [ a ] [ b ] [ c ] [ e ]
@@ -65,8 +65,8 @@ class EditTileListStateTest : SysuiTestCase() {
fun moveDragOnSidesOfLargeTile_listChanges() {
val draggedCell = TestEditTiles[4]
- underTest.onStarted(draggedCell)
- underTest.onMoved(4, true)
+ underTest.onStarted(draggedCell, DragType.Add)
+ underTest.onTargeting(4, true)
// Tile E goes to the right side of tile D, list is unchanged
// [ a ] [ b ] [ c ] [ X ]
@@ -74,7 +74,7 @@ class EditTileListStateTest : SysuiTestCase() {
assertThat(underTest.tiles.toStrings())
.isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
- underTest.onMoved(4, false)
+ underTest.onTargeting(4, false)
// Tile E goes to the left side of tile D, they swap positions
// [ a ] [ b ] [ c ] [ e ]
@@ -87,8 +87,8 @@ class EditTileListStateTest : SysuiTestCase() {
fun moveNewTile_tileIsAdded() {
val newTile = createEditTile("newTile", 2)
- underTest.onStarted(newTile)
- underTest.onMoved(5, false)
+ underTest.onStarted(newTile, DragType.Add)
+ underTest.onTargeting(5, false)
// New tile goes to index 5
// [ a ] [ b ] [ c ] [ X ]
@@ -102,7 +102,7 @@ class EditTileListStateTest : SysuiTestCase() {
@Test
fun movedTileOutOfBounds_tileDisappears() {
- underTest.onStarted(TestEditTiles[0])
+ underTest.onStarted(TestEditTiles[0], DragType.Add)
underTest.movedOutOfBounds()
assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 272491850c9c..af6f0cb2ec83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -615,7 +615,7 @@ private fun Shortcut(
}
.focusable(interactionSource = interactionSource)
.padding(8.dp)
- .semantics { contentDescription = shortcut.contentDescription }
+ .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription }
) {
Row(
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 35faa97db2fe..405ce8a8e5e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -44,19 +44,28 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
/** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */
interface DragAndDropState {
val draggedCell: SizedTile<EditTileViewModel>?
+ val draggedPosition: Offset
val dragInProgress: Boolean
+ val dragType: DragType?
fun isMoving(tileSpec: TileSpec): Boolean
- fun onStarted(cell: SizedTile<EditTileViewModel>)
+ fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType)
- fun onMoved(target: Int, insertAfter: Boolean)
+ fun onTargeting(target: Int, insertAfter: Boolean)
+
+ fun onMoved(offset: Offset)
fun movedOutOfBounds()
fun onDrop()
}
+enum class DragType {
+ Add,
+ Move,
+}
+
/**
* Registers a composable as a [DragAndDropTarget] to receive drop events. Use this outside the tile
* grid to catch out of bounds drops.
@@ -72,6 +81,10 @@ fun Modifier.dragAndDropRemoveZone(
val target =
remember(dragAndDropState) {
object : DragAndDropTarget {
+ override fun onMoved(event: DragAndDropEvent) {
+ dragAndDropState.onMoved(event.toOffset())
+ }
+
override fun onDrop(event: DragAndDropEvent): Boolean {
return dragAndDropState.draggedCell?.let {
onDrop(it.tile.tileSpec)
@@ -117,8 +130,11 @@ fun Modifier.dragAndDropTileList(
}
override fun onMoved(event: DragAndDropEvent) {
+ val offset = event.toOffset()
+ dragAndDropState.onMoved(offset)
+
// Drag offset relative to the list's top left corner
- val relativeDragOffset = event.dragOffsetRelativeTo(contentOffset())
+ val relativeDragOffset = offset - contentOffset()
val targetItem =
gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
// Check if the drag is on this item
@@ -126,7 +142,7 @@ fun Modifier.dragAndDropTileList(
}
targetItem?.let {
- dragAndDropState.onMoved(it.index, insertAfter(it, relativeDragOffset))
+ dragAndDropState.onTargeting(it.index, insertAfter(it, relativeDragOffset))
}
}
@@ -147,8 +163,8 @@ fun Modifier.dragAndDropTileList(
)
}
-private fun DragAndDropEvent.dragOffsetRelativeTo(offset: Offset): Offset {
- return toAndroidDragEvent().run { Offset(x, y) } - offset
+private fun DragAndDropEvent.toOffset(): Offset {
+ return toAndroidDragEvent().run { Offset(x, y) }
}
private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
@@ -163,6 +179,7 @@ private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
fun Modifier.dragAndDropTileSource(
sizedTile: SizedTile<EditTileViewModel>,
dragAndDropState: DragAndDropState,
+ dragType: DragType,
onDragStart: () -> Unit,
): Modifier {
val dragState by rememberUpdatedState(dragAndDropState)
@@ -172,7 +189,7 @@ fun Modifier.dragAndDropTileSource(
detectDragGesturesAfterLongPress(
onDrag = { _, _ -> },
onDragStart = {
- dragState.onStarted(sizedTile)
+ dragState.onStarted(sizedTile, dragType)
onDragStart()
// The tilespec from the ClipData transferred isn't actually needed as we're
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 14abfa2313d8..868855840922 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -17,10 +17,13 @@
package com.android.systemui.qs.panels.ui.compose
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.geometry.Offset
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -48,12 +51,17 @@ class EditTileListState(
private val columns: Int,
private val largeTilesSpan: Int,
) : DragAndDropState {
- private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
- override val draggedCell
- get() = _draggedCell.value
+ override var draggedCell by mutableStateOf<SizedTile<EditTileViewModel>?>(null)
+ private set
+
+ override var draggedPosition by mutableStateOf(Offset.Unspecified)
+ private set
+
+ override var dragType by mutableStateOf<DragType?>(null)
+ private set
override val dragInProgress: Boolean
- get() = _draggedCell.value != null
+ get() = draggedCell != null
private val _tiles: SnapshotStateList<GridCell> =
tiles.toGridCells(columns).toMutableStateList()
@@ -83,18 +91,19 @@ class EditTileListState(
}
override fun isMoving(tileSpec: TileSpec): Boolean {
- return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
+ return draggedCell?.let { it.tile.tileSpec == tileSpec } ?: false
}
- override fun onStarted(cell: SizedTile<EditTileViewModel>) {
- _draggedCell.value = cell
+ override fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType) {
+ draggedCell = cell
+ this.dragType = dragType
// Add spacers to the grid to indicate where the user can move a tile
regenerateGrid()
}
- override fun onMoved(target: Int, insertAfter: Boolean) {
- val draggedTile = _draggedCell.value ?: return
+ override fun onTargeting(target: Int, insertAfter: Boolean) {
+ val draggedTile = draggedCell ?: return
val fromIndex = indexOf(draggedTile.tile.tileSpec)
if (fromIndex == target) {
@@ -115,16 +124,26 @@ class EditTileListState(
regenerateGrid()
}
+ override fun onMoved(offset: Offset) {
+ draggedPosition = offset
+ }
+
override fun movedOutOfBounds() {
- val draggedTile = _draggedCell.value ?: return
+ val draggedTile = draggedCell ?: return
_tiles.removeIf { cell ->
cell is TileGridCell && cell.tile.tileSpec == draggedTile.tile.tileSpec
}
+ draggedPosition = Offset.Unspecified
+
+ // Regenerate spacers without the dragged tile
+ regenerateGrid()
}
override fun onDrop() {
- _draggedCell.value = null
+ draggedCell = null
+ draggedPosition = Offset.Unspecified
+ dragType = null
// Remove the spacers
regenerateGrid()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index a05747dd3ba2..d975f104d538 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -20,12 +20,16 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollFactory
+import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clipScrollableContainer
@@ -43,6 +47,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
@@ -69,6 +74,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -80,6 +86,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.MeasureScope
@@ -111,6 +118,7 @@ import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.DragAndDropState
+import com.android.systemui.qs.panels.ui.compose.DragType
import com.android.systemui.qs.panels.ui.compose.EditTileListState
import com.android.systemui.qs.panels.ui.compose.bounceableInfo
import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone
@@ -120,6 +128,9 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_DISTANCE
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding
import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer
@@ -139,8 +150,10 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.groupAndSort
import com.android.systemui.res.R
+import kotlin.math.abs
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
object TileType
@@ -201,8 +214,12 @@ fun DefaultEditTileGrid(
) { innerPadding ->
CompositionLocalProvider(LocalOverscrollFactory provides null) {
val scrollState = rememberScrollState()
- LaunchedEffect(listState.dragInProgress) {
- if (listState.dragInProgress) {
+
+ AutoScrollGrid(listState, scrollState, innerPadding)
+
+ LaunchedEffect(listState.dragType) {
+ // Only scroll to the top when adding a new tile, not when reordering existing ones
+ if (listState.dragInProgress && listState.dragType == DragType.Add) {
scrollState.animateScrollTo(0)
}
}
@@ -223,7 +240,7 @@ fun DefaultEditTileGrid(
AnimatedContent(
targetState = listState.dragInProgress,
modifier = Modifier.wrapContentSize(),
- label = "",
+ label = "QSEditHeader",
) { dragIsInProgress ->
EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) {
if (dragIsInProgress) {
@@ -243,34 +260,84 @@ fun DefaultEditTileGrid(
onSetTiles,
)
- // Hide available tiles when dragging
- AnimatedVisibility(
- visible = !listState.dragInProgress,
- enter = fadeIn(),
- exit = fadeOut(),
+ // Sets a minimum height to be used when available tiles are hidden
+ Box(
+ Modifier.fillMaxWidth()
+ .requiredHeightIn(AvailableTilesGridMinHeight)
+ .animateContentSize()
+ .dragAndDropRemoveZone(listState, onRemoveTile)
) {
- Column(
- verticalArrangement =
- spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
- modifier = modifier.fillMaxSize(),
+ // Using the fully qualified name here as a workaround for AnimatedVisibility
+ // not being available from a Box
+ androidx.compose.animation.AnimatedVisibility(
+ visible = !listState.dragInProgress,
+ enter = fadeIn(),
+ exit = fadeOut(),
) {
- EditGridHeader {
- Text(text = stringResource(id = R.string.drag_to_add_tiles))
- }
+ // Hide available tiles when dragging
+ Column(
+ verticalArrangement =
+ spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+ modifier = modifier.fillMaxSize(),
+ ) {
+ EditGridHeader {
+ Text(text = stringResource(id = R.string.drag_to_add_tiles))
+ }
- AvailableTileGrid(otherTiles, selectionState, columns, listState)
+ AvailableTileGrid(otherTiles, selectionState, columns, listState)
+ }
}
}
+ }
+ }
+ }
+}
- // Drop zone to remove tiles dragged out of the tile grid
- Spacer(
- modifier =
- Modifier.fillMaxWidth()
- .weight(1f)
- .dragAndDropRemoveZone(listState, onRemoveTile)
- )
+@OptIn(ExperimentalCoroutinesApi::class)
+@Composable
+private fun AutoScrollGrid(
+ listState: EditTileListState,
+ scrollState: ScrollState,
+ padding: PaddingValues,
+) {
+ val density = LocalDensity.current
+ val (top, bottom) =
+ remember(density) {
+ with(density) {
+ padding.calculateTopPadding().roundToPx() to
+ padding.calculateBottomPadding().roundToPx()
+ }
+ }
+ val scrollTarget by
+ remember(listState, scrollState, top, bottom) {
+ derivedStateOf {
+ val position = listState.draggedPosition
+ if (position.isSpecified) {
+ // Return the scroll target needed based on the position of the drag movement,
+ // or null if we don't need to scroll
+ val y = position.y.roundToInt()
+ when {
+ y < AUTO_SCROLL_DISTANCE + top -> 0
+ y > scrollState.viewportSize - bottom - AUTO_SCROLL_DISTANCE ->
+ scrollState.maxValue
+ else -> null
+ }
+ } else {
+ null
+ }
}
}
+ LaunchedEffect(scrollTarget) {
+ scrollTarget?.let {
+ // Change the duration of the animation based on the distance to maintain the
+ // same scrolling speed
+ val distance = abs(it - scrollState.value)
+ scrollState.animateScrollTo(
+ it,
+ animationSpec =
+ tween(durationMillis = distance * AUTO_SCROLL_SPEED, easing = LinearEasing),
+ )
+ }
}
}
@@ -423,7 +490,7 @@ private fun AvailableTileGrid(
}
fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp {
- return ((tileHeight + tilePadding) * rows) - tilePadding + gridPadding * 2
+ return ((tileHeight + tilePadding) * rows) + gridPadding * 2
}
private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
@@ -596,6 +663,7 @@ private fun TileGridCell(
.dragAndDropTileSource(
SizedTileImpl(cell.tile, cell.width),
dragAndDropState,
+ DragType.Move,
selectionState::unSelect,
)
.tileBackground(colors.background)
@@ -631,7 +699,11 @@ private fun AvailableTileGridCell(
onClick(onClickActionName) { false }
this.stateDescription = stateDescription
}
- .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) {
+ .dragAndDropTileSource(
+ SizedTileImpl(cell.tile, cell.width),
+ dragAndDropState,
+ DragType.Add,
+ ) {
selectionState.unSelect()
}
.tileBackground(colors.background)
@@ -739,7 +811,10 @@ private fun Modifier.tileBackground(color: Color): Modifier {
private object EditModeTileDefaults {
const val PLACEHOLDER_ALPHA = .3f
+ const val AUTO_SCROLL_DISTANCE = 100
+ const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel
val CurrentTilesGridPadding = 8.dp
+ val AvailableTilesGridMinHeight = 200.dp
@Composable
fun editTileColors(): TileColors =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index d2317e4f533d..fc720b836f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -87,7 +87,7 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0])
+ listState.onStarted(TestEditTiles[0], DragType.Add)
// Tile is being dragged, it should be replaced with a placeholder
composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
@@ -113,8 +113,8 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0])
- listState.onMoved(1, false)
+ listState.onStarted(TestEditTiles[0], DragType.Add)
+ listState.onTargeting(1, false)
listState.onDrop()
// Available tiles should re-appear
@@ -140,7 +140,7 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0])
+ listState.onStarted(TestEditTiles[0], DragType.Add)
listState.movedOutOfBounds()
listState.onDrop()
@@ -165,11 +165,11 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(createEditTile("newTile"))
+ listState.onStarted(createEditTile("newTile"), DragType.Add)
// Insert after tileD, which is at index 4
// [ a ] [ b ] [ c ] [ empty ]
// [ tile d ] [ e ]
- listState.onMoved(4, insertAfter = true)
+ listState.onTargeting(4, insertAfter = true)
listState.onDrop()
// Available tiles should re-appear
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
index 17e407e06ea5..ae9b8c85910f 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
@@ -28,15 +28,18 @@ import kotlinx.coroutines.flow.conflate
* Returns a [TFlow] that emits the value sampled from the [Transactional] produced by each emission
* of the original [TFlow], within the same transaction of the original emission.
*/
+@ExperimentalFrpApi
fun <A> TFlow<Transactional<A>>.sampleTransactionals(): TFlow<A> = map { it.sample() }
/** @see FrpTransactionScope.sample */
+@ExperimentalFrpApi
fun <A, B, C> TFlow<A>.sample(
state: TState<B>,
transform: suspend FrpTransactionScope.(A, B) -> C,
): TFlow<C> = map { transform(it, state.sample()) }
/** @see FrpTransactionScope.sample */
+@ExperimentalFrpApi
fun <A, B, C> TFlow<A>.sample(
transactional: Transactional<B>,
transform: suspend FrpTransactionScope.(A, B) -> C,
@@ -51,6 +54,7 @@ fun <A, B, C> TFlow<A>.sample(
*
* @see sample
*/
+@ExperimentalFrpApi
fun <A, B, C> TFlow<A>.samplePromptly(
state: TState<B>,
transform: suspend FrpTransactionScope.(A, B) -> C,
@@ -74,6 +78,7 @@ fun <A, B, C> TFlow<A>.samplePromptly(
* Returns a cold [Flow] that, when collected, emits from this [TFlow]. [network] is needed to
* transactionally connect to / disconnect from the [TFlow] when collection starts/stops.
*/
+@ExperimentalFrpApi
fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
@@ -81,6 +86,7 @@ fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
* Returns a cold [Flow] that, when collected, emits from this [TState]. [network] is needed to
* transactionally connect to / disconnect from the [TState] when collection starts/stops.
*/
+@ExperimentalFrpApi
fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
@@ -90,6 +96,7 @@ fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
*
* When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
*/
+@ExperimentalFrpApi
@JvmName("flowSpecToColdConflatedFlow")
fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
@@ -100,6 +107,7 @@ fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
*
* When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
*/
+@ExperimentalFrpApi
@JvmName("stateSpecToColdConflatedFlow")
fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
@@ -108,6 +116,7 @@ fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
* Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
* this [network], and then emits from the returned [TFlow].
*/
+@ExperimentalFrpApi
@JvmName("transactionalFlowToColdConflatedFlow")
fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
@@ -116,6 +125,7 @@ fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A
* Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
* this [network], and then emits from the returned [TState].
*/
+@ExperimentalFrpApi
@JvmName("transactionalStateToColdConflatedFlow")
fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
@@ -126,6 +136,7 @@ fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<
*
* When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
*/
+@ExperimentalFrpApi
@JvmName("statefulFlowToColdConflatedFlow")
fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
@@ -136,11 +147,13 @@ fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A>
*
* When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
*/
+@ExperimentalFrpApi
@JvmName("statefulStateToColdConflatedFlow")
fun <A> FrpStateful<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
/** Return a [TFlow] that emits from the original [TFlow] only when [state] is `true`. */
+@ExperimentalFrpApi
fun <A> TFlow<A>.filter(state: TState<Boolean>): TFlow<A> = filter { state.sample() }
private fun Iterable<Boolean>.allTrue() = all { it }
@@ -148,13 +161,15 @@ private fun Iterable<Boolean>.allTrue() = all { it }
private fun Iterable<Boolean>.anyTrue() = any { it }
/** Returns a [TState] that is `true` only when all of [states] are `true`. */
+@ExperimentalFrpApi
fun allOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.allTrue() }
/** Returns a [TState] that is `true` when any of [states] are `true`. */
+@ExperimentalFrpApi
fun anyOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.anyTrue() }
/** Returns a [TState] containing the inverse of the Boolean held by the original [TState]. */
-fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
+@ExperimentalFrpApi fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
/**
* Represents a modal FRP sub-network.
@@ -168,6 +183,7 @@ fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
*
* @see FrpStatefulMode
*/
+@ExperimentalFrpApi
fun interface FrpBuildMode<out A> {
/**
* Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
@@ -183,6 +199,7 @@ fun interface FrpBuildMode<out A> {
*
* @see FrpBuildMode
*/
+@ExperimentalFrpApi
val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>>
get() = frpSpec {
var modeChangeEvents by TFlowLoop<FrpBuildMode<A>>()
@@ -206,6 +223,7 @@ val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>>
*
* @see FrpBuildMode
*/
+@ExperimentalFrpApi
fun interface FrpStatefulMode<out A> {
/**
* Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
@@ -221,6 +239,7 @@ fun interface FrpStatefulMode<out A> {
*
* @see FrpBuildMode
*/
+@ExperimentalFrpApi
val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>>
get() = statefully {
var modeChangeEvents by TFlowLoop<FrpStatefulMode<A>>()
@@ -237,6 +256,7 @@ val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>>
* Runs [spec] in this [FrpBuildScope], and then re-runs it whenever [rebuildSignal] emits. Returns
* a [TState] that holds the result of the currently-active [FrpSpec].
*/
+@ExperimentalFrpApi
fun <A> FrpBuildScope.rebuildOn(rebuildSignal: TFlow<*>, spec: FrpSpec<A>): TState<A> =
rebuildSignal.map { spec }.holdLatestSpec(spec)
@@ -248,5 +268,6 @@ fun <A> FrpBuildScope.rebuildOn(rebuildSignal: TFlow<*>, spec: FrpSpec<A>): TSta
* stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
* ```
*/
+@ExperimentalFrpApi
val <A> TState<A>.transitions: TFlow<WithPrev<A, A>>
get() = stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
index be2eb4312476..b39dcc131b1d 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
@@ -22,16 +22,17 @@ import kotlinx.coroutines.CoroutineScope
/**
* Scope for external side-effects triggered by the Frp network. This still occurs within the
* context of a transaction, so general suspending calls are disallowed to prevent blocking the
- * transaction. You can use [frpCoroutineScope] to [launch] new coroutines to perform long-running
- * asynchronous work. This scope is alive for the duration of the containing [FrpBuildScope] that
- * this side-effect scope is running in.
+ * transaction. You can use [frpCoroutineScope] to [launch][kotlinx.coroutines.launch] new
+ * coroutines to perform long-running asynchronous work. This scope is alive for the duration of the
+ * containing [FrpBuildScope] that this side-effect scope is running in.
*/
@RestrictsSuspension
@ExperimentalFrpApi
interface FrpEffectScope : FrpTransactionScope {
/**
* A [CoroutineScope] whose lifecycle lives for as long as this [FrpEffectScope] is alive. This
- * is generally until the [Job] returned by [FrpBuildScope.effect] is cancelled.
+ * is generally until the [Job][kotlinx.coroutines.Job] returned by [FrpBuildScope.effect] is
+ * cancelled.
*/
@ExperimentalFrpApi val frpCoroutineScope: CoroutineScope
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
index b688eafe12e9..97252b4a199a 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
@@ -137,7 +137,7 @@ internal class LocalFrpNetwork(
override suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R {
val result = CompletableDeferred<R>(coroutineContext[Job])
@Suppress("DeferredResultUnused")
- network.transaction {
+ network.transaction("FrpNetwork.transact") {
val buildScope =
BuildScopeImpl(
stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
@@ -151,7 +151,7 @@ internal class LocalFrpNetwork(
override suspend fun activateSpec(spec: FrpSpec<*>) {
val job =
network
- .transaction {
+ .transaction("FrpNetwork.activateSpec") {
val buildScope =
BuildScopeImpl(
stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
@@ -166,7 +166,8 @@ internal class LocalFrpNetwork(
override fun <In, Out> coalescingMutableTFlow(
coalesce: (old: Out, new: In) -> Out,
getInitialValue: () -> Out,
- ): CoalescingMutableTFlow<In, Out> = CoalescingMutableTFlow(coalesce, network, getInitialValue)
+ ): CoalescingMutableTFlow<In, Out> =
+ CoalescingMutableTFlow(null, coalesce, network, getInitialValue)
override fun <T> mutableTFlow(): MutableTFlow<T> = MutableTFlow(network)
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
index c773d9c0f254..a175e2e20e46 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
@@ -467,12 +467,12 @@ fun <A> TState<TFlow<A>>.switchPromptly(): TFlow<A> {
@ExperimentalFrpApi
class CoalescingMutableTFlow<In, Out>
internal constructor(
+ internal val name: String?,
internal val coalesce: (old: Out, new: In) -> Out,
internal val network: Network,
private val getInitialValue: () -> Out,
internal val impl: InputNode<Out> = InputNode(),
) : TFlow<Out>() {
- internal val name: String? = null
internal val storage = AtomicReference(false to getInitialValue())
override fun toString(): String = "${this::class.simpleName}@$hashString"
@@ -490,7 +490,7 @@ internal constructor(
val (scheduled, _) = storage.getAndUpdate { (_, old) -> true to coalesce(old, value) }
if (!scheduled) {
@Suppress("DeferredResultUnused")
- network.transaction {
+ network.transaction("CoalescingMutableTFlow${name?.let { "($name)" }.orEmpty()}.emit") {
impl.visit(this, storage.getAndSet(false to getInitialValue()).second)
}
}
@@ -524,7 +524,9 @@ internal constructor(internal val network: Network, internal val impl: InputNode
val newEmit =
async(start = CoroutineStart.LAZY) {
jobOrNull?.join()
- network.transaction { impl.visit(this, value) }.await()
+ network
+ .transaction("MutableTFlow($name).emit") { impl.visit(this, value) }
+ .await()
}
jobOrNull = storage.getAndSet(newEmit)
newEmit.await()
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
index 982d6c30fac4..80e74748a375 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
@@ -121,7 +121,7 @@ fun <A, B, C> TState<A>.combineWith(
/**
* Splits a [TState] of pairs into a pair of [TFlows][TState], where each returned [TState] holds
- * hald of the original.
+ * half of the original.
*
* Shorthand for:
* ```kotlin
@@ -457,6 +457,7 @@ internal constructor(internal val network: Network, initialValue: Deferred<T>) :
private val input: CoalescingMutableTFlow<Deferred<T>, Deferred<T>?> =
CoalescingMutableTFlow(
+ name = null,
coalesce = { _, new -> new },
network = network,
getInitialValue = { null },
@@ -474,7 +475,7 @@ internal constructor(internal val network: Network, initialValue: Deferred<T>) :
.cached()
state = TStateSource(name, operatorName, initialValue, calm)
@Suppress("DeferredResultUnused")
- network.transaction {
+ network.transaction("MutableTState.init") {
calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
(connection, needsEval) ->
state.upstreamConnection = connection
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
index 90f1aea3e42f..7e6384925f38 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
@@ -34,9 +34,9 @@ import com.android.systemui.kairos.TFlowInit
import com.android.systemui.kairos.groupByKey
import com.android.systemui.kairos.init
import com.android.systemui.kairos.internal.util.childScope
-import com.android.systemui.kairos.internal.util.launchOnCancel
import com.android.systemui.kairos.internal.util.mapValuesParallel
import com.android.systemui.kairos.launchEffect
+import com.android.systemui.kairos.mergeLeft
import com.android.systemui.kairos.util.Just
import com.android.systemui.kairos.util.Maybe
import com.android.systemui.kairos.util.None
@@ -49,7 +49,6 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CompletableJob
-import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
@@ -86,8 +85,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
builder: suspend S.() -> Unit,
): TFlow<A> {
var job: Job? = null
- val stopEmitter = newStopEmitter()
- val handle = this.job.invokeOnCompletion { stopEmitter.emit(Unit) }
+ val stopEmitter = newStopEmitter("buildTFlow")
// Create a child scope that will be kept alive beyond the end of this transaction.
val childScope = coroutineScope.childScope()
lateinit var emitter: Pair<T, S>
@@ -99,7 +97,6 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
reenterBuildScope(this@BuildScopeImpl, childScope).runInBuildScope {
launchEffect {
builder(emitter.second)
- handle.dispose()
stopEmitter.emit(Unit)
}
}
@@ -110,7 +107,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
},
)
emitter = constructFlow(inputNode)
- return with(frpScope) { emitter.first.takeUntil(stopEmitter) }
+ return with(frpScope) { emitter.first.takeUntil(mergeLeft(stopEmitter, endSignal)) }
}
private fun <T> tFlowInternal(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T> =
@@ -134,7 +131,8 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
): TFlow<Out> =
buildTFlow(
constructFlow = { inputNode ->
- val flow = CoalescingMutableTFlow(coalesce, network, getInitialValue, inputNode)
+ val flow =
+ CoalescingMutableTFlow(null, coalesce, network, getInitialValue, inputNode)
flow to
object : FrpCoalescingProducerScope<In> {
override fun emit(value: In) {
@@ -164,11 +162,13 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
val subRef = AtomicReference<Maybe<Output<A>>>(null)
val childScope = coroutineScope.childScope()
// When our scope is cancelled, deactivate this observer.
- childScope.launchOnCancel(CoroutineName("TFlow.observeEffect")) {
+ childScope.coroutineContext.job.invokeOnCompletion {
subRef.getAndSet(None)?.let { output ->
if (output is Just) {
@Suppress("DeferredResultUnused")
- network.transaction { scheduleDeactivation(output.value) }
+ network.transaction("observeEffect cancelled") {
+ scheduleDeactivation(output.value)
+ }
}
}
}
@@ -215,7 +215,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
} else if (needsEval) {
outputNode.schedule(evalScope = stateScope.evalScope)
}
- } ?: childScope.cancel()
+ } ?: run { childScope.cancel() }
}
return childScope.coroutineContext.job
}
@@ -229,10 +229,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
"mapBuild",
mapImpl({ init.connect(evalScope = this) }) { spec ->
reenterBuildScope(outerScope = this@BuildScopeImpl, childScope)
- .runInBuildScope {
- val (result, _) = asyncScope { transform(spec) }
- result.get()
- }
+ .runInBuildScope { transform(spec) }
}
.cached(),
)
@@ -272,8 +269,9 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
return changes to FrpDeferredValue(initOut)
}
- private fun newStopEmitter(): CoalescingMutableTFlow<Unit, Unit> =
+ private fun newStopEmitter(name: String): CoalescingMutableTFlow<Unit, Unit> =
CoalescingMutableTFlow(
+ name = name,
coalesce = { _, _: Unit -> },
network = network,
getInitialValue = {},
@@ -299,17 +297,19 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope
}
private fun mutableChildBuildScope(): BuildScopeImpl {
- val stopEmitter = newStopEmitter()
+ val stopEmitter = newStopEmitter("mutableChildBuildScope")
val childScope = coroutineScope.childScope()
childScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) }
// Ensure that once this transaction is done, the new child scope enters the completing
// state (kept alive so long as there are child jobs).
- scheduleOutput(
- OneShot {
- // TODO: don't like this cast
- (childScope.coroutineContext.job as CompletableJob).complete()
- }
- )
+ // TODO: need to keep the scope alive if it's used to accumulate state.
+ // Otherwise, stopEmitter will emit early, due to the call to complete().
+ // scheduleOutput(
+ // OneShot {
+ // // TODO: don't like this cast
+ // (childScope.coroutineContext.job as CompletableJob).complete()
+ // }
+ // )
return BuildScopeImpl(
stateScope = StateScopeImpl(evalScope = stateScope.evalScope, endSignal = stopEmitter),
coroutineScope = childScope,
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
index af864e6c3496..69994ba6e866 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
@@ -66,8 +66,6 @@ internal interface NetworkScope : InitScope {
fun schedule(state: TStateSource<*>)
- suspend fun schedule(node: MuxNode<*, *, *>)
-
fun scheduleDeactivation(node: PushNode<*>)
fun scheduleDeactivation(output: Output<*>)
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
index f7ff15f0507b..af68a1e3d83c 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
@@ -188,6 +188,14 @@ internal sealed class MuxNode<K : Any, V, Output>(val lifecycle: MuxLifecycle<Ou
}
abstract fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean
+
+ fun schedule(evalScope: EvalScope) {
+ // TODO: Potential optimization
+ // Detect if this node is guaranteed to have a single upstream within this transaction,
+ // then bypass scheduling it. Instead immediately schedule its downstream and treat this
+ // MuxNode as a Pull (effectively making it a mapCheap).
+ depthTracker.schedule(evalScope.scheduler, this)
+ }
}
/** An input branch of a mux node, associated with a key. */
@@ -202,7 +210,7 @@ internal class MuxBranchNode<K : Any, V>(private val muxNode: MuxNode<K, V, *>,
val upstreamResult = upstream.getPushEvent(evalScope)
if (upstreamResult is Just) {
muxNode.upstreamData[key] = upstreamResult.value
- evalScope.schedule(muxNode)
+ muxNode.schedule(evalScope)
}
}
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index 08bee855831a..3b9502a5d812 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -409,7 +409,7 @@ internal fun <K : Any, A> switchDeferredImpl(
// Schedule for evaluation if any switched-in nodes have already emitted within
// this transaction.
if (muxNode.upstreamData.isNotEmpty()) {
- evalScope.schedule(muxNode)
+ muxNode.schedule(evalScope)
}
return muxNode.takeUnless { muxNode.switchedIn.isEmpty() && !isIndirect }
}
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index cdfafa943121..b291c879b449 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -75,7 +75,7 @@ internal class MuxPromptMovingNode<K : Any, V>(
if (depthTracker.dirty_depthIncreased()) {
depthTracker.schedule(evalScope.compactor, node = this)
}
- evalScope.schedule(this)
+ schedule(evalScope)
} else {
val compactDownstream = depthTracker.isDirty()
if (evalResult != null || compactDownstream) {
@@ -291,7 +291,7 @@ internal class MuxPromptPatchNode<K : Any, V>(private val muxNode: MuxPromptMovi
val upstreamResult = upstream.getPushEvent(evalScope)
if (upstreamResult is Just) {
muxNode.patchData = upstreamResult.value
- evalScope.schedule(muxNode)
+ muxNode.schedule(evalScope)
}
}
@@ -451,7 +451,7 @@ internal fun <K : Any, A> switchPromptImpl(
// Schedule for evaluation if any switched-in nodes or the patches node have
// already emitted within this transaction.
if (movingNode.patchData != null || movingNode.upstreamData.isNotEmpty()) {
- evalScope.schedule(movingNode)
+ movingNode.schedule(evalScope)
}
return movingNode.takeUnless { it.patches == null && it.switchedIn.isEmpty() }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
index 83440d85419e..599b18695034 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
@@ -81,11 +81,6 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope {
stateWrites.add(state)
}
- // TODO: weird that we have this *and* scheduler exposed
- override suspend fun schedule(node: MuxNode<*, *, *>) {
- scheduler.schedule(node.depthTracker.dirty_directDepth, node)
- }
-
override fun scheduleDeactivation(node: PushNode<*>) {
deactivations.add(node)
}
@@ -123,12 +118,12 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope {
}
/** Evaluates [block] inside of a new transaction when the network is ready. */
- fun <R> transaction(block: suspend EvalScope.() -> R): Deferred<R> =
+ fun <R> transaction(reason: String, block: suspend EvalScope.() -> R): Deferred<R> =
CompletableDeferred<R>(parent = coroutineScope.coroutineContext.job).also { onResult ->
val job =
coroutineScope.launch {
inputScheduleChan.send(
- ScheduledAction(onStartTransaction = block, onResult = onResult)
+ ScheduledAction(reason, onStartTransaction = block, onResult = onResult)
)
}
onResult.invokeOnCompletion { job.cancel() }
@@ -227,6 +222,7 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope {
}
internal class ScheduledAction<T>(
+ val reason: String,
private val onResult: CompletableDeferred<T>? = null,
private val onStartTransaction: suspend EvalScope.() -> T,
) {
diff --git a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
index 7294ef33cbc5..688adae8fcae 100644
--- a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
+++ b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
@@ -1300,6 +1300,26 @@ class KairosTests {
}
@Test
+ fun buildScope_stateAccumulation() = runFrpTest { network ->
+ val input = network.mutableTFlow<Unit>()
+ var observedCount: Int? = null
+ activateSpec(network) {
+ val (c, j) = asyncScope { input.fold(0) { _, x -> x + 1 } }
+ deferredBuildScopeAction { c.get().observe { observedCount = it } }
+ }
+ runCurrent()
+ assertEquals(0, observedCount)
+
+ input.emit(Unit)
+ runCurrent()
+ assertEquals(1, observedCount)
+
+ input.emit(Unit)
+ runCurrent()
+ assertEquals(2, observedCount)
+ }
+
+ @Test
fun effect() = runFrpTest { network ->
val input = network.mutableTFlow<Unit>()
var effectRunning = false
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 09b8e212bfad..1799b7715e5c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4177,6 +4177,12 @@ public class AudioService extends IAudioService.Stub
// Stream mute changed, fire the intent.
Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
+ if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) {
+ intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioSystem.STREAM_BLUETOOTH_SCO);
+ // in this case broadcast for both sco and voice_call streams the mute status
+ sendBroadcastToAll(intent, null /* options */);
+ }
intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
sendBroadcastToAll(intent, null /* options */);
}
@@ -9670,9 +9676,16 @@ public class AudioService extends IAudioService.Stub
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
oldIndex);
-
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
- mStreamType);
+ int extraStreamType = mStreamType;
+ // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
+ if (isStreamBluetoothSco(mStreamType)) {
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ AudioSystem.STREAM_BLUETOOTH_SCO);
+ extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO;
+ } else {
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ mStreamType);
+ }
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
streamAlias);
@@ -9683,9 +9696,21 @@ public class AudioService extends IAudioService.Stub
" aliased streams: " + aliasStreamIndexes;
}
AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, aliasStreamIndexesString, index, oldIndex));
+ extraStreamType, aliasStreamIndexesString, index, oldIndex));
+ if (extraStreamType != mStreamType) {
+ AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+ mStreamType, aliasStreamIndexesString, index, oldIndex));
+ }
}
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
+ if (extraStreamType != mStreamType) {
+ // send multiple intents in case we merged voice call and bt sco streams
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ mStreamType);
+ // do not use the options in thid case which could discard
+ // the previous intent
+ sendBroadcastToAll(mVolumeChanged, null);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index c0ddebeb9868..837adf004df7 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -78,24 +78,22 @@ public class InstallDependencyHelper {
mPackageInstallerService = packageInstallerService;
}
- void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
- Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+ void resolveLibraryDependenciesIfNeeded(List<SharedLibraryInfo> missingLibraries,
+ PackageLite pkg, Computer snapshot, int userId, Handler handler,
+ OutcomeReceiver<Void, PackageManagerException> origCallback) {
CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
try {
- resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
- } catch (PackageManagerException e) {
- callback.onError(e);
+ resolveLibraryDependenciesIfNeededInternal(
+ missingLibraries, pkg, snapshot, userId, handler, callback);
} catch (Exception e) {
onError(callback, e.getMessage());
}
}
- private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
- int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
- final List<SharedLibraryInfo> missing =
- mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
-
+ private void resolveLibraryDependenciesIfNeededInternal(List<SharedLibraryInfo> missing,
+ PackageLite pkg, Computer snapshot, int userId, Handler handler,
+ CallOnceProxy callback) {
if (missing.isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
@@ -129,6 +127,11 @@ public class InstallDependencyHelper {
}
}
+ List<SharedLibraryInfo> getMissingSharedLibraries(PackageLite pkg)
+ throws PackageManagerException {
+ return mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+ }
+
void notifySessionComplete(int sessionId) {
if (DEBUG) {
Slog.d(TAG, "Session complete for " + sessionId);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index b0fe3a97af6e..c96c160deb0f 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -170,6 +170,8 @@ final class InstallRequest {
private final boolean mHasAppMetadataFileFromInstaller;
private boolean mKeepArtProfile = false;
+ private final boolean mDependencyInstallerEnabled;
+ private final int mMissingSharedLibraryCount;
// New install
InstallRequest(InstallingSession params) {
@@ -190,6 +192,8 @@ final class InstallRequest {
mRequireUserAction = params.mRequireUserAction;
mPreVerifiedDomains = params.mPreVerifiedDomains;
mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile;
+ mDependencyInstallerEnabled = params.mDependencyInstallerEnabled;
+ mMissingSharedLibraryCount = params.mMissingSharedLibraryCount;
}
// Install existing package as user
@@ -209,6 +213,8 @@ final class InstallRequest {
mInstallerUidForInstallExisting = installerUid;
mSystem = isSystem;
mHasAppMetadataFileFromInstaller = false;
+ mDependencyInstallerEnabled = false;
+ mMissingSharedLibraryCount = 0;
}
// addForInit
@@ -231,6 +237,8 @@ final class InstallRequest {
mRequireUserAction = USER_ACTION_UNSPECIFIED;
mDisabledPs = disabledPs;
mHasAppMetadataFileFromInstaller = false;
+ mDependencyInstallerEnabled = false;
+ mMissingSharedLibraryCount = 0;
}
@Nullable
@@ -1069,4 +1077,12 @@ final class InstallRequest {
boolean isKeepArtProfile() {
return mKeepArtProfile;
}
+
+ int getMissingSharedLibraryCount() {
+ return mMissingSharedLibraryCount;
+ }
+
+ boolean isDependencyInstallerEnabled() {
+ return mDependencyInstallerEnabled;
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index ccc117566989..6a2bf83ba368 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -103,6 +103,8 @@ class InstallingSession {
final DomainSet mPreVerifiedDomains;
final boolean mHasAppMetadataFile;
@Nullable final String mDexoptCompilerFilter;
+ final boolean mDependencyInstallerEnabled;
+ final int mMissingSharedLibraryCount;
// For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -138,13 +140,16 @@ class InstallingSession {
mPreVerifiedDomains = null;
mHasAppMetadataFile = false;
mDexoptCompilerFilter = null;
+ mDependencyInstallerEnabled = false;
+ mMissingSharedLibraryCount = 0;
}
InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm,
- boolean hasAppMetadatafile) {
+ boolean hasAppMetadatafile, boolean dependencyInstallerEnabled,
+ int missingSharedLibraryCount) {
mPm = pm;
mUser = user;
mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -175,6 +180,8 @@ class InstallingSession {
mPreVerifiedDomains = preVerifiedDomains;
mHasAppMetadataFile = hasAppMetadatafile;
mDexoptCompilerFilter = sessionParams.dexoptCompilerFilter;
+ mDependencyInstallerEnabled = dependencyInstallerEnabled;
+ mMissingSharedLibraryCount = missingSharedLibraryCount;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 891d66a5d238..c6760431116e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -31,6 +31,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STOR
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
@@ -109,6 +110,7 @@ import android.content.pm.PackageInstaller.UserActionReason;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLite;
@@ -540,6 +542,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private DomainSet mPreVerifiedDomains;
+ private AtomicBoolean mDependencyInstallerEnabled = new AtomicBoolean();
+ private AtomicInteger mMissingSharedLibraryCount = new AtomicInteger();
+
static class FileEntry {
private final int mIndex;
private final InstallationFile mFile;
@@ -3232,6 +3237,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (Flags.sdkDependencyInstaller()
&& params.isAutoInstallDependenciesEnabled
&& !isMultiPackage()) {
+ mDependencyInstallerEnabled.set(true);
resolveLibraryDependenciesIfNeeded();
} else {
install();
@@ -3241,8 +3247,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void resolveLibraryDependenciesIfNeeded() {
synchronized (mLock) {
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
- mPm.snapshotComputer(), userId, mHandler,
+ List<SharedLibraryInfo> missingLibraries = new ArrayList<>();
+ try {
+ missingLibraries = mInstallDependencyHelper.getMissingSharedLibraries(mPackageLite);
+ } catch (PackageManagerException e) {
+ handleDependencyResolutionFailure(e);
+ } catch (Exception e) {
+ handleDependencyResolutionFailure(
+ new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage()));
+ }
+
+ mMissingSharedLibraryCount.set(missingLibraries.size());
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingLibraries,
+ mPackageLite, mPm.snapshotComputer(), userId, mHandler,
new OutcomeReceiver<>() {
@Override
@@ -3252,14 +3270,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void onError(@NonNull PackageManagerException e) {
- final String completeMsg = ExceptionUtils.getCompleteMessage(e);
- setSessionFailed(e.error, completeMsg);
- onSessionDependencyResolveFailure(e.error, completeMsg);
+ handleDependencyResolutionFailure(e);
}
});
}
}
+ private void handleDependencyResolutionFailure(@NonNull PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ setSessionFailed(e.error, completeMsg);
+ onSessionDependencyResolveFailure(e.error, completeMsg);
+ PackageMetrics.onDependencyInstallationFailure(
+ sessionId, getPackageName(), e.error, mInstallerUid, params,
+ mMissingSharedLibraryCount.get());
+ }
+
/**
* Stages this session for install and returns a
* {@link InstallingSession} representing this new staged state.
@@ -3327,7 +3352,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) {
return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm,
- mHasAppMetadataFile);
+ mHasAppMetadataFile, mDependencyInstallerEnabled.get(),
+ mMissingSharedLibraryCount.get());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0acadb129f2b..856d6a726da5 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -32,7 +32,9 @@ import android.app.admin.SecurityLog;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.DataLoaderType;
import android.content.pm.Flags;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -173,7 +175,10 @@ final class PackageMetrics {
mInstallRequest.isInstallInherit() /* is_inherit */,
mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
mInstallRequest.isInstallMove() /* is_move_install */,
- false /* is_staged */
+ false /* is_staged */,
+ mInstallRequest
+ .isDependencyInstallerEnabled() /* is_install_dependencies_enabled */,
+ mInstallRequest.getMissingSharedLibraryCount() /* missing_dependencies_count */
);
}
@@ -323,7 +328,53 @@ final class PackageMetrics {
verifyingSession.isInherit() /* is_inherit */,
false /* is_installing_existing_as_user */,
false /* is_move_install */,
- verifyingSession.isStaged() /* is_staged */
+ verifyingSession.isStaged() /* is_staged */,
+ false /* is_install_dependencies_enabled */,
+ 0 /* missing_dependencies_count */
+ );
+ }
+
+ static void onDependencyInstallationFailure(
+ int sessionId, String packageName, int errorCode, int installerPackageUid,
+ PackageInstaller.SessionParams params, int missingDependenciesCount) {
+ if (params == null) {
+ return;
+ }
+ int dataLoaderType = DataLoaderType.NONE;
+ if (params.dataLoaderParams != null) {
+ dataLoaderType = params.dataLoaderParams.getType();
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+ sessionId /* session_id */,
+ packageName /* package_name */,
+ INVALID_UID /* uid */,
+ null /* user_ids */,
+ null /* user_types */,
+ null /* original_user_ids */,
+ null /* original_user_types */,
+ errorCode /* public_return_code */,
+ 0 /* internal_error_code */,
+ 0 /* apks_size_bytes */,
+ 0 /* version_code */,
+ null /* install_steps */,
+ null /* step_duration_millis */,
+ 0 /* total_duration_millis */,
+ 0 /* install_flags */,
+ installerPackageUid /* installer_package_uid */,
+ INVALID_UID /* original_installer_package_uid */,
+ dataLoaderType /* data_loader_type */,
+ params.requireUserAction /* user_action_required_type */,
+ (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 /* is_instant */,
+ false /* is_replace */,
+ false /* is_system */,
+ params.mode
+ == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING /* is_inherit */,
+ false /* is_installing_existing_as_user */,
+ false /* is_move_install */,
+ params.isStaged /* is_staged */,
+ true /* is_install_dependencies_enabled */,
+ missingDependenciesCount /* missing_dependencies_count */
);
}
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 542ae8eb9207..dd60a155f2fb 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -16,11 +16,7 @@
package com.android.server.pm;
-import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
-import static android.content.Intent.EXTRA_PACKAGE_NAME;
-import static android.content.Intent.EXTRA_VERSION_CODE;
import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
@@ -40,9 +36,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.DataLoaderType;
@@ -68,7 +62,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.incremental.IncrementalManager;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 1208b6ef396f..08ceb61e14a8 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -142,6 +142,8 @@ public class ActivityServiceConnectionsHolder<T> {
/** Used by {@link ActivityRecord#dump}. */
@Override
public String toString() {
- return String.valueOf(mConnections);
+ synchronized (mActivity) {
+ return String.valueOf(mConnections);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b6ca75c5f0d..58319f4cdabc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2673,7 +2673,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (outRelayoutResult != null) {
if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
- && win.mSyncSeqId > lastSyncSeqId) {
+ && win.mSyncSeqId > lastSyncSeqId && !displayContent.mWaitingForConfig) {
outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers()
? win.mSyncSeqId
: -1;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index 0304a74f7654..cd8d415bdfa2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -21,11 +21,7 @@ import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.SharedLibraryInfo;
@@ -90,32 +86,15 @@ public class InstallDependencyHelperTest {
}
@Test
- public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
- throws Exception {
- doThrow(new PackageManagerException(new Exception("xyz")))
- .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
-
- PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
- CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
- 0, mHandler, callback);
- callback.assertFailure();
-
- assertThat(callback.error).hasMessageThat().contains("xyz");
- }
-
- @Test
public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
// Return a non-empty list as missing dependency
PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
List<SharedLibraryInfo> missingDependency = Collections.singletonList(
mock(SharedLibraryInfo.class));
- when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
- .thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
- 0, mHandler, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg,
+ mComputer, 0, mHandler, callback);
callback.assertFailure();
assertThat(callback.error).hasMessageThat().contains(
@@ -128,12 +107,10 @@ public class InstallDependencyHelperTest {
// Return an empty list as missing dependency
PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
List<SharedLibraryInfo> missingDependency = Collections.emptyList();
- when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
- .thenReturn(missingDependency);
CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
- mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
- 0, mHandler, callback);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg,
+ mComputer, 0, mHandler, callback);
callback.assertSuccess();
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index eaeed2a0a9fa..b1a5b1bebc59 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -17,6 +17,7 @@
package android.telephony.satellite.stub;
import android.annotation.NonNull;
+import android.hardware.radio.network.IRadioNetwork;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.IBooleanConsumer;
@@ -586,7 +587,11 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
* SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
* SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ *
+ * @deprecated Use
+ * {@link IRadioNetwork#setSatellitePlmn(int, int, String[], String[])}.
*/
+ @Deprecated
public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
@NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
@NonNull IIntegerConsumer resultCallback) {
@@ -608,7 +613,11 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
* SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
* SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ *
+ * @deprecated Use
+ * {@link IRadioNetwork#setSatelliteEnabledForCarrier(int, int, boolean)}.
*/
+ @Deprecated
public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
@NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
// stub implementation
@@ -629,7 +638,11 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
* SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
* SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+ *
+ * @deprecated Use
+ * {@link IRadioNetwork#isSatelliteEnabledForCarrier(int, int)}.
*/
+ @Deprecated
public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
// stub implementation