diff options
39 files changed, 768 insertions, 454 deletions
diff --git a/INPUT_OWNERS b/INPUT_OWNERS index 06ead06fc13a..9b1016e7b7e9 100644 --- a/INPUT_OWNERS +++ b/INPUT_OWNERS @@ -1,4 +1,5 @@ # Bug component: 136048 +# Please assign bugs to android-framework-input-triage@. arpitks@google.com asmitapoddar@google.com hcutts@google.com diff --git a/TEST_MAPPING b/TEST_MAPPING index 49384cde5803..5db077220b20 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -137,13 +137,14 @@ "name": "CtsStrictJavaPackagesTestCases" } ], - "postsubmit-ravenwood": [ + "ravenwood-presubmit": [ { "name": "CtsUtilTestCasesRavenwood", - "host": true, - "file_patterns": [ - "[Rr]avenwood" - ] + "host": true + }, + { + "name": "RavenwoodBivalentTest", + "host": true } ], "postsubmit-managedprofile-stress": [ diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java index d7b1c9a2d3a2..f20b1706129b 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java @@ -43,7 +43,6 @@ import java.util.concurrent.atomic.AtomicLong; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -143,7 +142,7 @@ public final class ClientSocketPerfTest { // Always use the same server for consistency across the benchmarks. server = config.serverFactory().newServer( - ChannelType.CHANNEL, config.messageSize(), config.protocol().getProtocols(), + config.messageSize(), config.protocol().getProtocols(), ciphers(config)); server.setMessageProcessor(new ServerEndpoint.MessageProcessor() { @@ -197,7 +196,6 @@ public final class ClientSocketPerfTest { */ @Test @Parameters(method = "getParams") - @Ignore("b/351034205") public void time(Config config) throws Exception { reset(); setup(config); diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java index 0655f45726ba..ba2acb8a5205 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java @@ -43,10 +43,10 @@ public enum EndpointFactory { factories.clientFactory, channelType, port, protocols, ciphers); } - public ServerEndpoint newServer(ChannelType channelType, int messageSize, + public ServerEndpoint newServer(int messageSize, String[] protocols, String[] ciphers) throws IOException { return new ServerEndpoint(factories.serverFactory, factories.serverSocketFactory, - channelType, messageSize, protocols, ciphers); + messageSize, protocols, ciphers); } private static final class Factories { diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java index 3631c3f29287..1e4f12460936 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java @@ -34,8 +34,6 @@ import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -import org.conscrypt.ChannelType; - /** * A simple socket-based test server. */ @@ -63,7 +61,6 @@ final class ServerEndpoint { } private final ServerSocket serverSocket; - private final ChannelType channelType; private final SSLSocketFactory socketFactory; private final int messageSize; private final String[] protocols; @@ -78,11 +75,10 @@ final class ServerEndpoint { private volatile Future<?> processFuture; ServerEndpoint(SSLSocketFactory socketFactory, SSLServerSocketFactory serverSocketFactory, - ChannelType channelType, int messageSize, String[] protocols, + int messageSize, String[] protocols, String[] cipherSuites) throws IOException { - this.serverSocket = channelType.newServerSocket(serverSocketFactory); + this.serverSocket = serverSocketFactory.createServerSocket(); this.socketFactory = socketFactory; - this.channelType = channelType; this.messageSize = messageSize; this.protocols = protocols; this.cipherSuites = cipherSuites; @@ -134,7 +130,7 @@ final class ServerEndpoint { if (stopping) { return; } - socket = channelType.accept(serverSocket, socketFactory); + socket = (SSLSocket) serverSocket.accept(); socket.setEnabledProtocols(protocols); socket.setEnabledCipherSuites(cipherSuites); diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java index 8916a3c55a9a..af3c405eab82 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java @@ -43,7 +43,6 @@ import androidx.test.filters.LargeTest; import junitparams.JUnitParamsRunner; import junitparams.Parameters; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,8 +130,7 @@ public final class ServerSocketPerfTest { final ChannelType channelType = config.channelType(); - server = config.serverFactory().newServer( - channelType, config.messageSize(), + server = config.serverFactory().newServer(config.messageSize(), new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config)); server.setMessageProcessor(new MessageProcessor() { @Override @@ -202,7 +200,6 @@ public final class ServerSocketPerfTest { @Test @Parameters(method = "getParams") - @Ignore("b/351034205") public void throughput(Config config) throws Exception { setup(config); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d71f3b6884ae..23cd3ce965ff 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -143,6 +143,7 @@ cc_defaults { "aconfig_text_flags_c_lib", "server_configurable_flags", "libaconfig_storage_read_api_cc", + "libgraphicsenv", ], static_libs: [ "libEGL_blobCache", diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 1217b47664dd..b6476c9d466f 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -42,6 +42,11 @@ constexpr bool hdr_10bit_plus() { constexpr bool initialize_gl_always() { return false; } + +constexpr bool skip_eglmanager_telemetry() { + return false; +} + constexpr bool resample_gainmap_regions() { return false; } @@ -103,6 +108,7 @@ float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number bool Properties::clipSurfaceViews = false; bool Properties::hdr10bitPlus = false; +bool Properties::skipTelemetry = false; bool Properties::resampleGainmapRegions = false; int Properties::timeoutMultiplier = 1; @@ -183,6 +189,8 @@ bool Properties::load() { hwui_flags::resample_gainmap_regions()); timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1); + skipTelemetry = base::GetBoolProperty(PROPERTY_SKIP_EGLMANAGER_TELEMETRY, + hwui_flags::skip_eglmanager_telemetry()); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 73e80ce4afd0..db471527b861 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -234,6 +234,8 @@ enum DebugLevel { */ #define PROPERTY_INITIALIZE_GL_ALWAYS "debug.hwui.initialize_gl_always" +#define PROPERTY_SKIP_EGLMANAGER_TELEMETRY "debug.hwui.skip_eglmanager_telemetry" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -342,6 +344,7 @@ public: static bool clipSurfaceViews; static bool hdr10bitPlus; + static bool skipTelemetry; static bool resampleGainmapRegions; static int timeoutMultiplier; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 13c0b00daa21..a1f51687b077 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -99,6 +99,13 @@ flag { } flag { + name: "skip_eglmanager_telemetry" + namespace: "core_graphics" + description: "Skip telemetry in EglManager's calls to eglCreateContext to avoid polluting telemetry" + bug: "347911216" +} + +flag { name: "resample_gainmap_regions" namespace: "core_graphics" description: "Resample gainmaps when decoding regions, to improve visual quality" diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 708b0113e13e..60104452f4c0 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -19,6 +19,7 @@ #include <EGL/eglext.h> #include <GLES/gl.h> #include <cutils/properties.h> +#include <graphicsenv/GpuStatsInfo.h> #include <log/log.h> #include <sync/sync.h> #include <utils/Trace.h> @@ -366,6 +367,10 @@ void EglManager::createContext() { contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); contextAttributes.push_back(Properties::contextPriority); } + if (Properties::skipTelemetry) { + contextAttributes.push_back(EGL_TELEMETRY_HINT_ANDROID); + contextAttributes.push_back(android::GpuStatsInfo::SKIP_TELEMETRY); + } contextAttributes.push_back(EGL_NONE); mEglContext = eglCreateContext( mEglDisplay, EglExtensions.noConfigContext ? ((EGLConfig) nullptr) : mEglConfig, diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 9ad3e3c0af0f..170cb4546d0c 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -91,8 +91,7 @@ public class UninstallerActivity extends Activity { // be stale, if e.g. the app was uninstalled while the activity was destroyed. super.onCreate(null); - // TODO(b/318521110) Enable PIA v2 for archive dialog. - if (usePiaV2() && !isTv() && !isArchiveDialog(getIntent())) { + if (usePiaV2() && !isTv()) { Log.i(TAG, "Using Pia V2"); boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); @@ -225,11 +224,6 @@ public class UninstallerActivity extends Activity { showConfirmationDialog(); } - private boolean isArchiveDialog(Intent intent) { - return (intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) - & PackageManager.DELETE_ARCHIVE) != 0; - } - /** * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} * to archive an app if requested. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index 186b69b4107b..3b0faf0cb56c 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -736,7 +736,8 @@ class InstallRepository(private val context: Context) { val appInfo = packageManager.getApplicationInfo( pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES ) - if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) { + // If the package is archived, treat it as an update case. + if (!appInfo.isArchived && appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) { return false } } catch (e: PackageManager.NameNotFoundException) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt index 0091a3e8b2cf..96525f645004 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt @@ -32,6 +32,8 @@ import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageInstaller import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags +import android.content.pm.PackageManager.PackageInfoFlags import android.content.pm.VersionedPackage import android.graphics.drawable.Icon import android.os.Build @@ -51,6 +53,9 @@ import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionF import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame +import com.android.packageinstaller.v2.model.UninstallAborted.Companion.ABORT_REASON_UNINSTALL_DONE +import android.content.pm.Flags as PmFlags +import android.multiuser.Flags as MultiuserFlags class UninstallRepository(private val context: Context) { @@ -71,6 +76,7 @@ class UninstallRepository(private val context: Context) { private var uninstallFromAllUsers = false private var isClonedApp = false private var uninstallId = 0 + private var deleteFlags = 0 fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage { this.intent = intent @@ -155,7 +161,9 @@ class UninstallRepository(private val context: Context) { try { targetAppInfo = packageManager.getApplicationInfo( targetPackageName!!, - PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong()) + ApplicationInfoFlags.of( + PackageManager.MATCH_ANY_USER.toLong() or PackageManager.MATCH_ARCHIVED_PACKAGES + ) ) } catch (e: PackageManager.NameNotFoundException) { Log.e(LOG_TAG, "Unable to get packageName") @@ -180,9 +188,27 @@ class UninstallRepository(private val context: Context) { } } + parseDeleteFlags(intent) + return UninstallReady() } + /** + * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent} + * to archive an app if requested. + * + * Do not parse other flags because developers might pass here any flags which might cause + * unintended behaviour. + * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}. + */ + private fun parseDeleteFlags(intent: Intent) { + val flags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0) + val archive = flags and PackageManager.DELETE_ARCHIVE + val keepData = flags and PackageManager.DELETE_KEEP_DATA + + deleteFlags = archive or keepData + } + fun generateUninstallDetails(): UninstallStage { val messageBuilder = StringBuilder() @@ -201,6 +227,8 @@ class UninstallRepository(private val context: Context) { } val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 + val isArchive = + PmFlags.archiving() && ((deleteFlags and PackageManager.DELETE_ARCHIVE) != 0) val myUserHandle = Process.myUserHandle() val isSingleUser = isSingleUser() @@ -215,34 +243,54 @@ class UninstallRepository(private val context: Context) { ) ) } else if (uninstallFromAllUsers && !isSingleUser) { - messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users)) + val messageString = if (isArchive) { + context.getString(R.string.archive_application_text_all_users) + } else { + context.getString(R.string.uninstall_application_text_all_users) + } + messageBuilder.append(messageString) } else if (uninstalledUser != myUserHandle) { // Uninstalling user is issuing uninstall for another user val customUserManager = context.createContextAsUser(uninstalledUser!!, 0) .getSystemService(UserManager::class.java) val userName = customUserManager!!.userName - var messageString = context.getString( - R.string.uninstall_application_text_user, - userName - ) + + var messageString = if (isArchive) { + context.getString(R.string.archive_application_text_user, userName) + } else { + context.getString(R.string.uninstall_application_text_user, userName) + } + if (userManager!!.isSameProfileGroup(myUserHandle, uninstalledUser!!)) { if (customUserManager.isManagedProfile) { - messageString = context.getString( + messageString = if (isArchive) { + context.getString( + R.string.archive_application_text_current_user_work_profile, userName + ) + } else { + context.getString( R.string.uninstall_application_text_current_user_work_profile, userName - ) + ) + } } else if (customUserManager.isCloneProfile){ isClonedApp = true messageString = context.getString( R.string.uninstall_application_text_current_user_clone_profile ) } else if (Flags.allowPrivateProfile() - && android.multiuser.Flags.enablePrivateSpaceFeatures() + && MultiuserFlags.enablePrivateSpaceFeatures() && customUserManager.isPrivateProfile ) { // TODO(b/324244123): Get these Strings from a User Property API. - messageString = context.getString( + messageString = if (isArchive) { + context.getString( + R.string.archive_application_text_current_user_private_profile, userName + ) + } else { + context.getString( R.string.uninstall_application_text_current_user_private_profile - ) + ) + } } } messageBuilder.append(messageString) @@ -262,6 +310,8 @@ class UninstallRepository(private val context: Context) { targetAppLabel ) ) + } else if (isArchive) { + messageBuilder.append(context.getString(R.string.archive_application_text)) } else { messageBuilder.append(context.getString(R.string.uninstall_application_text)) } @@ -270,15 +320,21 @@ class UninstallRepository(private val context: Context) { val title = if (isClonedApp) { context.getString(R.string.cloned_app_label, targetAppLabel) + } else if (isArchive) { + context.getString(R.string.archiving_app_label, targetAppLabel) } else { targetAppLabel.toString() } var suggestToKeepAppData = false try { - val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0) + val pkgInfo = packageManager.getPackageInfo( + targetPackageName!!, PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES) + ) suggestToKeepAppData = - pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData() + pkgInfo.applicationInfo != null + && pkgInfo.applicationInfo!!.hasFragileUserData() + && !isArchive } catch (e: PackageManager.NameNotFoundException) { Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e) } @@ -291,7 +347,7 @@ class UninstallRepository(private val context: Context) { ) } - return UninstallUserActionRequired(title, message, appDataSize) + return UninstallUserActionRequired(title, message, appDataSize, isArchive) } /** @@ -444,10 +500,11 @@ class UninstallRepository(private val context: Context) { callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message) // Since the caller already received the results, just finish the app at this point - uninstallResult.value = null + uninstallResult.value = UninstallAborted(ABORT_REASON_UNINSTALL_DONE) return } val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false) + if (returnResult || callingActivity != null) { val intent = Intent() intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus) @@ -717,6 +774,7 @@ class UninstallRepository(private val context: Context) { ): Boolean { var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0 flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0 + flags = flags or deleteFlags return try { context.createContextAsUser(targetUser, 0) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt index f086209fe498..316e8b7e3a73 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt @@ -38,7 +38,8 @@ class UninstallReady : UninstallStage(STAGE_READY) data class UninstallUserActionRequired( val title: String? = null, val message: String? = null, - val appDataSize: Long = 0 + val appDataSize: Long = 0, + val isArchive: Boolean = false ) : UninstallStage(STAGE_USER_ACTION_REQUIRED) data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) : @@ -96,6 +97,11 @@ data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED dialogTextResource = R.string.user_is_not_allowed_dlg_text } + ABORT_REASON_UNINSTALL_DONE -> { + dialogTitleResource = 0 + dialogTextResource = 0 + } + else -> { dialogTitleResource = 0 dialogTextResource = R.string.generic_error_dlg_text @@ -107,6 +113,7 @@ data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED const val ABORT_REASON_GENERIC_ERROR = 0 const val ABORT_REASON_APP_UNAVAILABLE = 1 const val ABORT_REASON_USER_NOT_ALLOWED = 2 + const val ABORT_REASON_UNINSTALL_DONE = 3 } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java index 87af1ae200ca..524b4e6a0e63 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java @@ -60,7 +60,7 @@ public class UninstallConfirmationFragment extends DialogFragment { Log.i(LOG_TAG, "Creating " + LOG_TAG + "\n" + mDialogData); AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()) .setTitle(mDialogData.getTitle()) - .setPositiveButton(R.string.ok, + .setPositiveButton(mDialogData.isArchive() ? R.string.archive : R.string.ok, (dialogInt, which) -> mUninstallActionListener.onPositiveResponse( mKeepData != null && mKeepData.isChecked())) .setNegativeButton(R.string.cancel, diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 03149928249b..17c3fd124f66 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -493,6 +493,16 @@ flag { } flag { + name: "status_bar_switch_to_spn_from_data_spn" + namespace: "systemui" + description: "Fix usage of the SPN broadcast extras" + bug: "350812372" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "haptic_volume_slider" namespace: "systemui" description: "Adds haptic feedback to the volume slider." diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 85d5d6a41965..808e6666e6e6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -26,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.layout -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView @@ -59,16 +58,8 @@ fun SceneScope.MediaCarousel( return } - val density = LocalDensity.current val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded) - val layoutWidth = 0 - val layoutHeight = with(density) { mediaHeight.toPx() }.toInt() - - // Notify controller to size the carousel for the current space - mediaHost.measurementInput = MeasurementInput(layoutWidth, layoutHeight) - carouselController.setSceneContainerSize(layoutWidth, layoutHeight) - MovableElement( key = MediaCarousel.Elements.Content, modifier = modifier.height(mediaHeight).fillMaxWidth(), diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index c5d7b25827ea..46c5c188344e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -888,8 +888,6 @@ constructor( heightInSceneContainerPx = height mediaCarouselScrollHandler.playerWidthPlusPadding = width + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) - mediaContent.minimumWidth = widthInSceneContainerPx - mediaContent.minimumHeight = heightInSceneContainerPx updatePlayers(recreateMedia = true) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index 88a28bf998da..091b886c7ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -1210,7 +1210,6 @@ constructor( (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding val location = when { - mediaFlags.isSceneContainerEnabled() -> desiredLocation dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY onCommunal -> LOCATION_COMMUNAL_HUB (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index b48b40986f73..875e505db1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -94,6 +94,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -173,6 +174,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private float mActiveRadius; private FeatureFlags mFeatureFlags; private UserTracker mUserTracker; + private VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor; public enum BroadcastNotifyDialog { ACTION_FIRST_LAUNCH, @@ -195,6 +197,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, PowerExemptionManager powerExemptionManager, KeyguardManager keyGuardManager, FeatureFlags featureFlags, + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor, UserTracker userTracker) { mContext = context; mPackageName = packageName; @@ -209,6 +212,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mFeatureFlags = featureFlags; mUserTracker = userTracker; mToken = token; + mVolumePanelGlobalStateInteractor = volumePanelGlobalStateInteractor; InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); @@ -436,7 +440,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, launchIntent.putExtra(EXTRA_ROUTE_ID, routeId); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mCallback.dismissDialog(); - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } } @@ -447,7 +451,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, if (launchIntent != null) { launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mCallback.dismissDialog(); - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } } @@ -951,10 +955,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, deepLinkIntent.putExtra( Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, PAGE_CONNECTED_DEVICES_KEY); - mActivityStarter.startActivity(deepLinkIntent, true, controller); + startActivity(deepLinkIntent, controller); return; } - mActivityStarter.startActivity(launchIntent, true, controller); + startActivity(launchIntent, controller); } void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender, @@ -998,6 +1002,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, broadcastSender, controller); @@ -1244,6 +1249,13 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return !device.isVolumeFixed(); } + private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) { + // Media Output dialog can be shown from the volume panel. This makes sure the panel is + // closed when navigating to another activity, so it doesn't stays on top of it + mVolumePanelGlobalStateInteractor.setVisible(false); + mActivityStarter.startActivity(intent, true, controller); + } + @Override public void onDevicesUpdated(List<NearbyDevice> nearbyDevices) throws RemoteException { mNearbyDeviceInfoMap.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 104d4b5427d4..65a59f50d1bf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1339,6 +1339,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump "NotificationPanelViewController.updateResources"); if (splitShadeChanged) { + if (isPanelVisibleBecauseOfHeadsUp()) { + // workaround for b/324642496, because HUNs set state to OPENING + onPanelStateChanged(STATE_CLOSED); + } onSplitShadeEnabledChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9d13a17d8e02..cb3e26b9f8ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1804,6 +1804,20 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationEntry childEntry, NotificationEntry containerEntry ); + + /** + * Called when resetting the alpha value for content views + */ + void logResetAllContentAlphas( + NotificationEntry entry + ); + + /** + * Called when resetting the alpha value for content views is skipped + */ + void logSkipResetAllContentAlphas( + NotificationEntry entry + ); } /** @@ -3001,6 +3015,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.animate().cancel(); } resetAllContentAlphas(); + } else { + mLogger.logSkipResetAllContentAlphas(getEntry()); } mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); @@ -3186,6 +3202,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void resetAllContentAlphas() { + mLogger.logResetAllContentAlphas(getEntry()); mPrivateLayout.setAlpha(1f); mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null); mPublicLayout.setAlpha(1f); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 4c76e3284dbe..c31a2cb8908b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -195,6 +195,20 @@ public class ExpandableNotificationRowController implements NotifViewController ) { mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry); } + + @Override + public void logResetAllContentAlphas( + NotificationEntry entry + ) { + mLogBufferLogger.logResetAllContentAlphas(entry); + } + + @Override + public void logSkipResetAllContentAlphas( + NotificationEntry entry + ) { + mLogBufferLogger.logSkipResetAllContentAlphas(entry); + } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt index 4f5a04f2bdc9..b1e90329e01a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt @@ -128,6 +128,24 @@ constructor( { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" } ) } + + fun logResetAllContentAlphas(entry: NotificationEntry) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { str1 = entry.logKey }, + { "resetAllContentAlphas: $str1" } + ) + } + + fun logSkipResetAllContentAlphas(entry: NotificationEntry) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { str1 = entry.logKey }, + { "Skip resetAllContentAlphas: $str1" } + ) + } } private const val TAG = "NotifRow" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 2e54972c4950..9cbfc440ab16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -198,7 +198,8 @@ constructor( fun logServiceProvidersUpdatedBroadcast(intent: Intent) { val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false) - val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN) + val spn = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + val dataSpn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN) val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false) val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) @@ -208,12 +209,13 @@ constructor( { bool1 = showSpn str1 = spn + str2 = dataSpn bool2 = showPlmn - str2 = plmn + str3 = plmn }, { "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." + - " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2" + " showSpn=$bool1 spn=$str1 dataSpn=$str2 showPlmn=$bool2 plmn=$str3" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index 99ed2d99c749..85bbe7e53493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -21,6 +21,8 @@ import android.telephony.TelephonyManager.EXTRA_DATA_SPN import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN +import android.telephony.TelephonyManager.EXTRA_SPN +import com.android.systemui.Flags.statusBarSwitchToSpnFromDataSpn import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger @@ -96,7 +98,13 @@ sealed interface NetworkNameModel : Diffable<NetworkNameModel> { fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false) - val spn = getStringExtra(EXTRA_DATA_SPN) + val spn = + if (statusBarSwitchToSpnFromDataSpn()) { + getStringExtra(EXTRA_SPN) + } else { + getStringExtra(EXTRA_DATA_SPN) + } + val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false) val plmn = getStringExtra(EXTRA_PLMN) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index d5cd86ec0b76..c8cc6b5fdf93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -50,14 +50,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import org.junit.Before; import org.junit.Test; @@ -73,6 +77,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private static final String TEST_PACKAGE = "test_package"; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class); private MediaController mMediaController = mock(MediaController.class); @@ -122,6 +128,9 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE); mMediaControllers.add(mMediaController); when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -139,6 +148,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); // Using a fake package will cause routing operations to fail, so we intercept diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 87d224579e95..189a56145d27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -51,14 +51,18 @@ import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import com.google.common.base.Strings; @@ -81,6 +85,8 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { private static final String BROADCAST_CODE_TEST = "112233"; private static final String BROADCAST_CODE_UPDATE_TEST = "11223344"; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class); @@ -123,6 +129,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST); when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn( BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8)); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -140,6 +149,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 856bc0b78215..714fad9d7478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -72,15 +72,19 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import com.google.common.collect.ImmutableList; @@ -158,11 +162,16 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + private FeatureFlags mFlags = mock(FeatureFlags.class); private View mDialogLaunchView = mock(View.class); private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class); final Notification mNotification = mock(Notification.class); + private final VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); private Context mSpyContext; private String mPackageName = null; @@ -194,6 +203,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn( mCachedBluetoothDeviceManager); + mMediaOutputController = new MediaOutputController( mSpyContext, @@ -210,6 +220,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); @@ -304,6 +315,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.start(mCb); @@ -346,6 +358,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.start(mCb); @@ -602,6 +615,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.start(mCb); reset(mCb); @@ -636,6 +650,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.start(mCb); reset(mCb); @@ -683,6 +698,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); @@ -710,6 +726,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class); @@ -990,6 +1007,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); @@ -1193,6 +1211,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + mVolumePanelGlobalStateInteractor, mUserTracker); testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index f20b04ae0e5c..90c2930f8e49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -54,14 +54,18 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.kosmos.Kosmos; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor; +import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractorKosmosKt; import org.junit.After; import org.junit.Before; @@ -84,6 +88,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this); + // Mock private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class); private MediaController mMediaController = mock(MediaController.class); @@ -136,6 +142,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mMediaSessionManager.getActiveSessionsForUser(any(), Mockito.eq(userHandle))).thenReturn( mMediaControllers); + VolumePanelGlobalStateInteractor volumePanelGlobalStateInteractor = + VolumePanelGlobalStateInteractorKosmosKt.getVolumePanelGlobalStateInteractor( + mKosmos); mMediaOutputController = new MediaOutputController( @@ -153,6 +162,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mPowerExemptionManager, mKeyguardManager, mFlags, + volumePanelGlobalStateInteractor, mUserTracker); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = makeTestDialog(mMediaOutputController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 8fd0b31b82f3..171520f72269 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN @@ -59,6 +60,7 @@ import android.telephony.TelephonyManager.DATA_UNKNOWN import android.telephony.TelephonyManager.ERI_OFF import android.telephony.TelephonyManager.ERI_ON import android.telephony.TelephonyManager.EXTRA_CARRIER_ID +import android.telephony.TelephonyManager.EXTRA_DATA_SPN import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN @@ -69,6 +71,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic @@ -85,7 +88,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig -import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength @@ -93,8 +95,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.Mobil import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor @@ -112,6 +112,8 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @@ -807,6 +809,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_usesBroadcastInfo_returnsDerived() = testScope.runTest { var latest: NetworkNameModel? = null @@ -815,14 +818,34 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + // spnIntent() sets all values to true and test strings + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) job.cancel() } @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_usesBroadcastInfo_returnsDerived_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) + + // spnIntent() sets all values to true and test strings + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + + job.cancel() + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_broadcastNotForThisSubId_keepsOldValue() = testScope.runTest { var latest: NetworkNameModel? = null @@ -831,22 +854,48 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) + + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + + // WHEN an intent with a different subId is sent + val wrongSubIntent = spnIntent(subId = 101) + + captor.lastValue.onReceive(context, wrongSubIntent) + + // THEN the previous intent's name is still used + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + + job.cancel() + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_broadcastNotForThisSubId_keepsOldValue_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) // WHEN an intent with a different subId is sent val wrongSubIntent = spnIntent(subId = 101) - captor.value!!.onReceive(context, wrongSubIntent) + captor.lastValue.onReceive(context, wrongSubIntent) // THEN the previous intent's name is still used - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_broadcastHasNoData_updatesToDefault() = testScope.runTest { var latest: NetworkNameModel? = null @@ -855,9 +904,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) val intentWithoutInfo = spnIntent( @@ -865,7 +914,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { showPlmn = false, ) - captor.value!!.onReceive(context, intentWithoutInfo) + captor.lastValue.onReceive(context, intentWithoutInfo) assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) @@ -873,6 +922,34 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_broadcastHasNoData_updatesToDefault_flagOff() = + testScope.runTest { + var latest: NetworkNameModel? = null + val job = underTest.networkName.onEach { latest = it }.launchIn(this) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) + + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + + val intentWithoutInfo = + spnIntent( + showSpn = false, + showPlmn = false, + ) + + captor.lastValue.onReceive(context, intentWithoutInfo) + + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + + job.cancel() + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() = testScope.runTest { // Use the [StateFlow.value] getter so we can prove that the collection happens @@ -884,10 +961,172 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { val intent = spnIntent() val captor = argumentCaptor<BroadcastReceiver>() verify(context).registerReceiver(captor.capture(), any()) - captor.value!!.onReceive(context, intent) + captor.lastValue.onReceive(context, intent) + + // The value is still there despite no active subscribers + assertThat(underTest.networkName.value) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers_flagOff() = + testScope.runTest { + // Use the [StateFlow.value] getter so we can prove that the collection happens + // even when there is no [Job] + + // Starts out default + assertThat(underTest.networkName.value).isEqualTo(DEFAULT_NAME_MODEL) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.lastValue.onReceive(context, intent) // The value is still there despite no active subscribers - assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP)) + assertThat(underTest.networkName.value) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_allFieldsSet_doesNotUseDataSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_allFieldsSet_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + fun networkName_showPlmn_noShowSPN() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = false, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN")) + } + + @Test + @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_flagOff() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = DATA_SPN, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN")) } @Test @@ -1128,14 +1367,16 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private fun spnIntent( subId: Int = SUB_1_ID, showSpn: Boolean = true, - spn: String = SPN, + spn: String? = SPN, + dataSpn: String? = DATA_SPN, showPlmn: Boolean = true, - plmn: String = PLMN, + plmn: String? = PLMN, ): Intent = Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply { putExtra(EXTRA_SUBSCRIPTION_INDEX, subId) putExtra(EXTRA_SHOW_SPN, showSpn) putExtra(EXTRA_SPN, spn) + putExtra(EXTRA_DATA_SPN, dataSpn) putExtra(EXTRA_SHOW_PLMN, showPlmn) putExtra(EXTRA_PLMN, plmn) } @@ -1148,6 +1389,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { private const val SEP = "-" private const val SPN = "testSpn" + private const val DATA_SPN = "testDataSpn" private const val PLMN = "testPlmn" } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index db48835a9b82..0f50260a55d2 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -961,14 +961,17 @@ public class ZenModeHelper { if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null && condition.source == SOURCE_USER_ACTION) { // Apply as override, instead of actual condition. + // If the new override is the reverse of a previous (still active) override, try + // removing the previous override, as long as the resulting state, based on the + // previous owner-provided condition, is the desired one (active or inactive). + // This allows the rule owner to resume controlling the rule after + // snoozing-unsnoozing or activating-stopping. if (condition.state == STATE_TRUE) { - // Manually turn on a rule -> Apply override. - rule.setConditionOverride(OVERRIDE_ACTIVATE); + rule.resetConditionOverride(); + if (!rule.isAutomaticActive()) { + rule.setConditionOverride(OVERRIDE_ACTIVATE); + } } else if (condition.state == STATE_FALSE) { - // Manually turn off a rule. If the rule was manually activated before, reset - // override -- but only if this will not result in the rule turning on - // immediately because of a previously snoozed condition! In that case, apply - // deactivate-override. rule.resetConditionOverride(); if (rule.isAutomaticActive()) { rule.setConditionOverride(OVERRIDE_DEACTIVATE); diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java index 48a9311c0374..b03af4c3734a 100644 --- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java @@ -26,9 +26,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; -import android.view.InsetsSource; -import android.view.InsetsState; -import android.view.RoundedCorner; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; @@ -44,12 +41,17 @@ class AppCompatLetterboxPolicy { private final ActivityRecord mActivityRecord; @NonNull private final LetterboxPolicyState mLetterboxPolicyState; + @NonNull + private final AppCompatRoundedCorners mAppCompatRoundedCorners; private boolean mLastShouldShowLetterboxUi; AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord) { mActivityRecord = activityRecord; mLetterboxPolicyState = new LetterboxPolicyState(); + // TODO (b/358334569) Improve cutout logic dependency on app compat. + mAppCompatRoundedCorners = new AppCompatRoundedCorners(mActivityRecord, + this::isLetterboxedNotForDisplayCutout); } /** Cleans up {@link Letterbox} if it exists.*/ @@ -105,7 +107,7 @@ class AppCompatLetterboxPolicy { if (shouldNotLayoutLetterbox(w)) { return; } - updateRoundedCornersIfNeeded(w); + mAppCompatRoundedCorners.updateRoundedCornersIfNeeded(w); updateWallpaperForLetterbox(w); if (shouldShowLetterboxUi(w)) { mLetterboxPolicyState.layoutLetterboxIfNeeded(w); @@ -138,94 +140,20 @@ class AppCompatLetterboxPolicy { @VisibleForTesting @Nullable Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { - // We don't want corner radius on the window. - // In the case the ActivityRecord requires a letterboxed animation we never want - // rounded corners on the window because rounded corners are applied at the - // animation-bounds surface level and rounded corners on the window would interfere - // with that leading to unexpected rounded corner positioning during the animation. - return null; - } - - final Rect cropBounds = new Rect(mActivityRecord.getBounds()); - - // In case of translucent activities we check if the requested size is different from - // the size provided using inherited bounds. In that case we decide to not apply rounded - // corners because we assume the specific layout would. This is the case when the layout - // of the translucent activity uses only a part of all the bounds because of the use of - // LayoutParams.WRAP_CONTENT. - final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController - .getTransparentPolicy(); - if (transparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth - || cropBounds.height() != mainWindow.mRequestedHeight)) { - return null; - } - - // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} - // because taskbar bounds used in {@link #adjustBoundsIfNeeded} - // are in screen coordinates - adjustBoundsForTaskbar(mainWindow, cropBounds); - - final float scale = mainWindow.mInvGlobalScale; - if (scale != 1f && scale > 0f) { - cropBounds.scale(scale); - } - - // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface - // control is in the top left corner of an app window so offsetting bounds - // accordingly. - cropBounds.offsetTo(0, 0); - return cropBounds; + return mAppCompatRoundedCorners.getCropBoundsIfNeeded(mainWindow); } - - // Returns rounded corners radius the letterboxed activity should have based on override in - // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. - // Device corners can be different on the right and left sides, but we use the same radius - // for all corners for consistency and pick a minimal bottom one for consistency with a - // taskbar rounded corners. + /** + * Returns rounded corners radius the letterboxed activity should have based on override in + * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. + * Device corners can be different on the right and left sides, but we use the same radius + * for all corners for consistency and pick a minimal bottom one for consistency with a + * taskbar rounded corners. + * + * @param mainWindow The {@link WindowState} to consider for the rounded corners calculation. + */ int getRoundedCornersRadius(@NonNull final WindowState mainWindow) { - if (!requiresRoundedCorners(mainWindow)) { - return 0; - } - final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord - .mAppCompatController.getAppCompatLetterboxOverrides(); - final int radius; - if (letterboxOverrides.getLetterboxActivityCornersRadius() >= 0) { - radius = letterboxOverrides.getLetterboxActivityCornersRadius(); - } else { - final InsetsState insetsState = mainWindow.getInsetsState(); - radius = Math.min( - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), - getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); - } - - final float scale = mainWindow.mInvGlobalScale; - return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; - } - - void adjustBoundsForTaskbar(@NonNull final WindowState mainWindow, - @NonNull final Rect bounds) { - // Rounded corners should be displayed above the taskbar. When taskbar is hidden, - // an insets frame is equal to a navigation bar which shouldn't affect position of - // rounded corners since apps are expected to handle navigation bar inset. - // This condition checks whether the taskbar is visible. - // Do not crop the taskbar inset if the window is in immersive mode - the user can - // swipe to show/hide the taskbar as an overlay. - // Adjust the bounds only in case there is an expanded taskbar, - // otherwise the rounded corners will be shown behind the navbar. - final InsetsSource expandedTaskbarOrNull = - AppCompatUtils.getExpandedTaskbarOrNull(mainWindow); - if (expandedTaskbarOrNull != null) { - // Rounded corners should be displayed above the expanded taskbar. - bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); - } - } - - private int getInsetsStateCornerRadius(@NonNull InsetsState insetsState, - @RoundedCorner.Position int position) { - final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); - return corner == null ? 0 : corner.getRadius(); + return mAppCompatRoundedCorners.getRoundedCornersRadius(mainWindow); } private void updateWallpaperForLetterbox(@NonNull WindowState mainWindow) { @@ -248,25 +176,6 @@ class AppCompatLetterboxPolicy { } } - void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) { - final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); - if (windowSurface == null || !windowSurface.isValid()) { - return; - } - - // cropBounds must be non-null for the cornerRadius to be ever applied. - mActivityRecord.getSyncTransaction() - .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) - .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); - } - - private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) { - final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord - .mAppCompatController.getAppCompatLetterboxOverrides(); - return isLetterboxedNotForDisplayCutout(mainWindow) - && letterboxOverrides.isLetterboxActivityCornersRounded(); - } - private boolean isLetterboxedNotForDisplayCutout(@NonNull WindowState mainWindow) { return shouldShowLetterboxUi(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout(); @@ -405,7 +314,7 @@ class AppCompatLetterboxPolicy { outBounds.set(mLetterbox.getInnerFrame()); final WindowState w = mActivityRecord.findMainWindow(); if (w != null) { - adjustBoundsForTaskbar(w, outBounds); + AppCompatUtils.adjustBoundsForTaskbar(w, outBounds); } } else { outBounds.setEmpty(); diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java new file mode 100644 index 000000000000..077ce00c6033 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java @@ -0,0 +1,143 @@ +/* + * 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.server.wm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.InsetsState; +import android.view.RoundedCorner; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.function.Predicate; + +/** + * Utility to unify rounded corners in app compat. + */ +class AppCompatRoundedCorners { + + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + private final Predicate<WindowState> mIsLetterboxedNotForDisplayCutout; + + AppCompatRoundedCorners(@NonNull ActivityRecord activityRecord, + @NonNull Predicate<WindowState> isLetterboxedNotForDisplayCutout) { + mActivityRecord = activityRecord; + mIsLetterboxedNotForDisplayCutout = isLetterboxedNotForDisplayCutout; + } + + void updateRoundedCornersIfNeeded(@NonNull final WindowState mainWindow) { + final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); + if (windowSurface == null || !windowSurface.isValid()) { + return; + } + + // cropBounds must be non-null for the cornerRadius to be ever applied. + mActivityRecord.getSyncTransaction() + .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) + .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); + } + + @VisibleForTesting + @Nullable + Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { + // We don't want corner radius on the window. + // In the case the ActivityRecord requires a letterboxed animation we never want + // rounded corners on the window because rounded corners are applied at the + // animation-bounds surface level and rounded corners on the window would interfere + // with that leading to unexpected rounded corner positioning during the animation. + return null; + } + + final Rect cropBounds = new Rect(mActivityRecord.getBounds()); + + // In case of translucent activities we check if the requested size is different from + // the size provided using inherited bounds. In that case we decide to not apply rounded + // corners because we assume the specific layout would. This is the case when the layout + // of the translucent activity uses only a part of all the bounds because of the use of + // LayoutParams.WRAP_CONTENT. + final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController + .getTransparentPolicy(); + if (transparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth + || cropBounds.height() != mainWindow.mRequestedHeight)) { + return null; + } + + // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} + // because taskbar bounds used in {@link #adjustBoundsIfNeeded} + // are in screen coordinates + AppCompatUtils.adjustBoundsForTaskbar(mainWindow, cropBounds); + + final float scale = mainWindow.mInvGlobalScale; + if (scale != 1f && scale > 0f) { + cropBounds.scale(scale); + } + + // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface + // control is in the top left corner of an app window so offsetting bounds + // accordingly. + cropBounds.offsetTo(0, 0); + return cropBounds; + } + + /** + * Returns rounded corners radius the letterboxed activity should have based on override in + * R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. + * Device corners can be different on the right and left sides, but we use the same radius + * for all corners for consistency and pick a minimal bottom one for consistency with a + * taskbar rounded corners. + * + * @param mainWindow The {@link WindowState} to consider for rounded corners calculation. + */ + int getRoundedCornersRadius(@NonNull final WindowState mainWindow) { + if (!requiresRoundedCorners(mainWindow)) { + return 0; + } + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + final int radius; + if (letterboxOverrides.getLetterboxActivityCornersRadius() >= 0) { + radius = letterboxOverrides.getLetterboxActivityCornersRadius(); + } else { + final InsetsState insetsState = mainWindow.getInsetsState(); + radius = Math.min( + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), + getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); + } + + final float scale = mainWindow.mInvGlobalScale; + return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; + } + + private static int getInsetsStateCornerRadius(@NonNull InsetsState insetsState, + @RoundedCorner.Position int position) { + final RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); + return corner == null ? 0 : corner.getRadius(); + } + + private boolean requiresRoundedCorners(@NonNull final WindowState mainWindow) { + final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord + .mAppCompatController.getAppCompatLetterboxOverrides(); + return mIsLetterboxedNotForDisplayCutout.test(mainWindow) + && letterboxOverrides.isLetterboxActivityCornersRounded(); + } + +} diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 91205fc757ad..2b842d99a3ab 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -37,7 +37,7 @@ import java.util.function.BooleanSupplier; /** * Utilities for App Compat policies and overrides. */ -class AppCompatUtils { +final class AppCompatUtils { /** * Lazy version of a {@link BooleanSupplier} which access an existing BooleanSupplier and @@ -232,6 +232,23 @@ class AppCompatUtils { return null; } + static void adjustBoundsForTaskbar(@NonNull final WindowState mainWindow, + @NonNull final Rect bounds) { + // Rounded corners should be displayed above the taskbar. When taskbar is hidden, + // an insets frame is equal to a navigation bar which shouldn't affect position of + // rounded corners since apps are expected to handle navigation bar inset. + // This condition checks whether the taskbar is visible. + // Do not crop the taskbar inset if the window is in immersive mode - the user can + // swipe to show/hide the taskbar as an overlay. + // Adjust the bounds only in case there is an expanded taskbar, + // otherwise the rounded corners will be shown behind the navbar. + final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow); + if (expandedTaskbarOrNull != null) { + // Rounded corners should be displayed above the expanded taskbar. + bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); + } + } + private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) { info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index f1e94deb8a45..a5085fc3147c 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -211,7 +211,6 @@ cc_defaults { "android.system.suspend-V1-ndk", "server_configurable_flags", "service.incremental", - "android.companion.virtualdevice.flags-aconfig-cc", ], static_libs: [ diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 9ec5ae5a8e51..6f7276079970 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -19,273 +19,22 @@ #include <android-base/unique_fd.h> #include <android/input.h> #include <android/keycodes.h> -#include <android_companion_virtualdevice_flags.h> -#include <errno.h> -#include <fcntl.h> #include <input/Input.h> #include <input/VirtualInputDevice.h> -#include <linux/uinput.h> -#include <math.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <utils/Log.h> -#include <map> -#include <set> #include <string> using android::base::unique_fd; namespace android { -namespace vd_flags = android::companion::virtualdevice::flags; - static constexpr jlong INVALID_PTR = 0; -enum class DeviceType { - KEYBOARD, - MOUSE, - TOUCHSCREEN, - DPAD, - STYLUS, - ROTARY_ENCODER, -}; - -static unique_fd invalidFd() { - return unique_fd(-1); -} - -/** Creates a new uinput device and assigns a file descriptor. */ -static unique_fd openUinput(const char* readableName, jint vendorId, jint productId, - const char* phys, DeviceType deviceType, jint screenHeight, - jint screenWidth) { - unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK))); - if (fd < 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - - ioctl(fd, UI_SET_PHYS, phys); - - ioctl(fd, UI_SET_EVBIT, EV_KEY); - ioctl(fd, UI_SET_EVBIT, EV_SYN); - switch (deviceType) { - case DeviceType::DPAD: - for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) { - ioctl(fd, UI_SET_KEYBIT, keyCode); - } - break; - case DeviceType::KEYBOARD: - for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) { - ioctl(fd, UI_SET_KEYBIT, keyCode); - } - break; - case DeviceType::MOUSE: - ioctl(fd, UI_SET_EVBIT, EV_REL); - ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); - ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); - ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); - ioctl(fd, UI_SET_KEYBIT, BTN_BACK); - ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD); - ioctl(fd, UI_SET_RELBIT, REL_X); - ioctl(fd, UI_SET_RELBIT, REL_Y); - ioctl(fd, UI_SET_RELBIT, REL_WHEEL); - ioctl(fd, UI_SET_RELBIT, REL_HWHEEL); - if (vd_flags::high_resolution_scroll()) { - ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); - ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES); - } - break; - case DeviceType::TOUCHSCREEN: - ioctl(fd, UI_SET_EVBIT, EV_ABS); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); - ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); - ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); - break; - case DeviceType::STYLUS: - ioctl(fd, UI_SET_EVBIT, EV_ABS); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER); - ioctl(fd, UI_SET_ABSBIT, ABS_X); - ioctl(fd, UI_SET_ABSBIT, ABS_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X); - ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y); - ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); - ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); - break; - case DeviceType::ROTARY_ENCODER: - ioctl(fd, UI_SET_EVBIT, EV_REL); - ioctl(fd, UI_SET_RELBIT, REL_WHEEL); - if (vd_flags::high_resolution_scroll()) { - ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES); - } - break; - default: - ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType)); - return invalidFd(); - } - - int version; - if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) { - uinput_setup setup; - memset(&setup, 0, sizeof(setup)); - strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE); - setup.id.version = 1; - setup.id.bustype = BUS_VIRTUAL; - setup.id.vendor = vendorId; - setup.id.product = productId; - if (deviceType == DeviceType::TOUCHSCREEN) { - uinput_abs_setup xAbsSetup; - xAbsSetup.code = ABS_MT_POSITION_X; - xAbsSetup.absinfo.maximum = screenWidth - 1; - xAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup yAbsSetup; - yAbsSetup.code = ABS_MT_POSITION_Y; - yAbsSetup.absinfo.maximum = screenHeight - 1; - yAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup majorAbsSetup; - majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; - majorAbsSetup.absinfo.maximum = screenWidth - 1; - majorAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup pressureAbsSetup; - pressureAbsSetup.code = ABS_MT_PRESSURE; - pressureAbsSetup.absinfo.maximum = 255; - pressureAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup slotAbsSetup; - slotAbsSetup.code = ABS_MT_SLOT; - slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1; - slotAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup trackingIdAbsSetup; - trackingIdAbsSetup.code = ABS_MT_TRACKING_ID; - trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1; - trackingIdAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno)); - return invalidFd(); - } - } else if (deviceType == DeviceType::STYLUS) { - uinput_abs_setup xAbsSetup; - xAbsSetup.code = ABS_X; - xAbsSetup.absinfo.maximum = screenWidth - 1; - xAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { - ALOGE("Error creating stylus uinput x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup yAbsSetup; - yAbsSetup.code = ABS_Y; - yAbsSetup.absinfo.maximum = screenHeight - 1; - yAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { - ALOGE("Error creating stylus uinput y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup tiltXAbsSetup; - tiltXAbsSetup.code = ABS_TILT_X; - tiltXAbsSetup.absinfo.maximum = 90; - tiltXAbsSetup.absinfo.minimum = -90; - if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) { - ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup tiltYAbsSetup; - tiltYAbsSetup.code = ABS_TILT_Y; - tiltYAbsSetup.absinfo.maximum = 90; - tiltYAbsSetup.absinfo.minimum = -90; - if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) { - ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno)); - return invalidFd(); - } - uinput_abs_setup pressureAbsSetup; - pressureAbsSetup.code = ABS_PRESSURE; - pressureAbsSetup.absinfo.maximum = 255; - pressureAbsSetup.absinfo.minimum = 0; - if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { - ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); - return invalidFd(); - } - } - if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - } else { - // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. - ALOGI("Falling back to version %d manual setup", version); - uinput_user_dev fallback; - memset(&fallback, 0, sizeof(fallback)); - strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); - fallback.id.version = 1; - fallback.id.bustype = BUS_VIRTUAL; - fallback.id.vendor = vendorId; - fallback.id.product = productId; - if (deviceType == DeviceType::TOUCHSCREEN) { - fallback.absmin[ABS_MT_POSITION_X] = 0; - fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1; - fallback.absmin[ABS_MT_POSITION_Y] = 0; - fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1; - fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0; - fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1; - fallback.absmin[ABS_MT_PRESSURE] = 0; - fallback.absmax[ABS_MT_PRESSURE] = 255; - } else if (deviceType == DeviceType::STYLUS) { - fallback.absmin[ABS_X] = 0; - fallback.absmax[ABS_X] = screenWidth - 1; - fallback.absmin[ABS_Y] = 0; - fallback.absmax[ABS_Y] = screenHeight - 1; - fallback.absmin[ABS_TILT_X] = -90; - fallback.absmax[ABS_TILT_X] = 90; - fallback.absmin[ABS_TILT_Y] = -90; - fallback.absmax[ABS_TILT_Y] = 90; - fallback.absmin[ABS_PRESSURE] = 0; - fallback.absmax[ABS_PRESSURE] = 255; - } - if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - } - - if (ioctl(fd, UI_DEV_CREATE) != 0) { - ALOGE("Error creating uinput device: %s", strerror(errno)); - return invalidFd(); - } - - return fd; -} - static unique_fd openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, - jstring phys, DeviceType deviceType, int screenHeight, - int screenWidth) { + jstring phys, DeviceType deviceType, jint screenHeight, + jint screenWidth) { ScopedUtfChars readableName(env, name); ScopedUtfChars readablePhys(env, phys); return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 212e61e10448..ed8ebc842be8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -4972,7 +4972,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_FALSE), - ORIGIN_APP, SYSTEM_UID); + ORIGIN_APP, CUSTOM_PKG_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) { @@ -6491,6 +6491,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", CUSTOM_PKG_UID); + Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, + SOURCE_CONTEXT); ZenRule zenRule; mZenModeHelper.setAutomaticZenRuleState(ruleId, @@ -6508,6 +6510,57 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(zenRule.isAutomaticActive()).isFalse(); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(zenRule.condition).isNull(); + + // Bonus check: app has resumed control over the rule and can now turn it on. + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivationAndThenReactivation_removesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, + SOURCE_CONTEXT); + Condition autoOff = new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, + SOURCE_CONTEXT); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOn); + + // Bonus check: app has resumed control over the rule and can now turn it off. + mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff, ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isEqualTo(autoOff); } @Test @@ -6521,7 +6574,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(ruleId, new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), - ORIGIN_APP, SYSTEM_UID); + ORIGIN_APP, CUSTOM_PKG_UID); ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(zenRuleOn.isAutomaticActive()).isTrue(); assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE); |