diff options
178 files changed, 3117 insertions, 1097 deletions
diff --git a/apct-tests/perftests/aconfig/Android.bp b/apct-tests/perftests/aconfig/Android.bp new file mode 100644 index 000000000000..715923de1eb7 --- /dev/null +++ b/apct-tests/perftests/aconfig/Android.bp @@ -0,0 +1,39 @@ +// 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 { + default_team: "trendy_team_android_core_experiments", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "AconfigPerfTests", + srcs: ["src/**/*.java"], + static_libs: [ + "aconfig_device_paths_java_util", + "androidx.test.rules", + "apct-perftests-utils", + "collector-device-lib", + "truth", + ], + platform_apis: true, + certificate: "platform", + test_suites: ["device-tests"], + data: [":perfetto_artifacts"], +} diff --git a/apct-tests/perftests/aconfig/AndroidManifest.xml b/apct-tests/perftests/aconfig/AndroidManifest.xml new file mode 100644 index 000000000000..e9d7c176303f --- /dev/null +++ b/apct-tests/perftests/aconfig/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.aconfig"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.aconfig"/> + +</manifest>
\ No newline at end of file diff --git a/apct-tests/perftests/aconfig/AndroidTest.xml b/apct-tests/perftests/aconfig/AndroidTest.xml new file mode 100644 index 000000000000..036e0310def2 --- /dev/null +++ b/apct-tests/perftests/aconfig/AndroidTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<configuration description="Runs AconfigPerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="AconfigPerfTests.apk" /> + </target_preparer> + + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" /> + </target_preparer> + + + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path" /> + </metrics_collector> + + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.aconfig" /> + <option name="hidden-api-checks" value="false"/> + + <!-- Listener related args for collecting the traces and waiting for the device to stabilize. --> + <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" /> + <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. --> + <option name="instrumentation-arg" key="newRunListenerMode" value="true" /> + + <!-- ProcLoadListener related arguments --> + <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run --> + <option name="instrumentation-arg" key="procload-collector:per_run" value="true" /> + <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" /> + <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" /> + <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" /> + + <!-- PerfettoListener related arguments --> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" /> + <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" /> + + <option name="instrumentation-arg" key="newRunListenerMode" value="true" /> + + </test> +</configuration>
\ No newline at end of file diff --git a/apct-tests/perftests/aconfig/OWNERS b/apct-tests/perftests/aconfig/OWNERS new file mode 100644 index 000000000000..2202076593fb --- /dev/null +++ b/apct-tests/perftests/aconfig/OWNERS @@ -0,0 +1 @@ +file:platform/packages/modules/ConfigInfrastructure:/OWNERS
\ No newline at end of file diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java new file mode 100644 index 000000000000..df6e3c836256 --- /dev/null +++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 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 android.os.flagging; + +import static com.google.common.truth.Truth.assertThat; + +import android.aconfig.DeviceProtosTestUtil; +import android.aconfig.nano.Aconfig.parsed_flag; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@RunWith(Parameterized.class) +public class AconfigPackagePerfTest { + + @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Parameterized.Parameters(name = "isPlatform={0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product"); + private static List<parsed_flag> sFlags; + + @BeforeClass + public static void init() { + try { + sFlags = DeviceProtosTestUtil.loadAndParseFlagProtos(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Parameterized.Parameter(0) + + // if this variable is true, then the test query flags from system/product/vendor + // if this variable is false, then the test query flags from updatable partitions + public boolean mIsPlatform; + + @Test + public void timeAconfigPackageLoadOnePackage() { + String packageName = ""; + for (parsed_flag flag : sFlags) { + if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) { + packageName = flag.package_; + break; + } + } + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + AconfigPackage.load(packageName); + } + } + + @Test + public void timeAconfigPackageLoadMultiplePackages() { + // load num packages + int packageNum = 25; + Set<String> packageSet = new HashSet<>(); + for (parsed_flag flag : sFlags) { + if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) { + packageSet.add(flag.package_); + } + if (packageSet.size() >= packageNum) { + break; + } + } + List<String> packageList = new ArrayList(packageSet); + assertThat(packageList.size()).isAtLeast(packageNum); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + for (int i = 0; state.keepRunning(); i++) { + AconfigPackage.load(packageList.get(i % packageNum)); + } + } + + @Test + public void timeAconfigPackageGetBooleanFlagValue() { + // get one package contains num of flags + int flagNum = 20; + List<parsed_flag> l = findNumFlagsInSamePackage(flagNum, mIsPlatform); + List<String> flagName = new ArrayList<>(); + String packageName = l.get(0).package_; + for (parsed_flag flag : l) { + flagName.add(flag.name); + } + assertThat(flagName.size()).isAtLeast(flagNum); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + AconfigPackage ap = AconfigPackage.load(packageName); + for (int i = 0; state.keepRunning(); i++) { + ap.getBooleanFlagValue(flagName.get(i % flagNum), false); + } + } + + private static List<parsed_flag> findNumFlagsInSamePackage(int num, boolean isPlatform) { + Map<String, List<parsed_flag>> packageToFlag = new HashMap<>(); + List<parsed_flag> ret = new ArrayList<parsed_flag>(); + for (parsed_flag flag : sFlags) { + if (isPlatform == PLATFORM_CONTAINERS.contains(flag.container)) { + ret = + packageToFlag.computeIfAbsent( + flag.package_, k -> new ArrayList<parsed_flag>()); + ret.add(flag); + if (ret.size() >= num) { + break; + } + } + } + return ret; + } +} diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java index 1a7258a802df..4c3416523b99 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java @@ -20,6 +20,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.AutoCloseable; import java.net.InetAddress; import java.net.SocketException; import java.nio.channels.ClosedChannelException; @@ -33,7 +34,7 @@ import org.conscrypt.ChannelType; * Client-side endpoint. Provides basic services for sending/receiving messages from the client * socket. */ -final class ClientEndpoint { +final class ClientEndpoint implements AutoCloseable { private final SSLSocket socket; private InputStream input; private OutputStream output; @@ -56,6 +57,11 @@ final class ClientEndpoint { } } + @Override + public void close() { + stop(); + } + void stop() { try { socket.close(); 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 f20b1706129b..9e45c4ae23b5 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java @@ -44,24 +44,21 @@ import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import org.junit.Rule; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import junitparams.JUnitParamsRunner; import junitparams.Parameters; import android.conscrypt.ServerEndpoint.MessageProcessor; -/** - * Benchmark for comparing performance of server socket implementations. - */ +/** Benchmark for comparing performance of server socket implementations. */ @RunWith(JUnitParamsRunner.class) @LargeTest public final class ClientSocketPerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - /** - * Provider for the test configuration - */ + /** Provider for the test configuration */ private class Config { EndpointFactory a_clientFactory; EndpointFactory b_serverFactory; @@ -69,19 +66,22 @@ public final class ClientSocketPerfTest { String d_cipher; ChannelType e_channelType; PerfTestProtocol f_protocol; - Config(EndpointFactory clientFactory, - EndpointFactory serverFactory, - int messageSize, - String cipher, - ChannelType channelType, - PerfTestProtocol protocol) { - a_clientFactory = clientFactory; - b_serverFactory = serverFactory; - c_messageSize = messageSize; - d_cipher = cipher; - e_channelType = channelType; - f_protocol = protocol; + + Config( + EndpointFactory clientFactory, + EndpointFactory serverFactory, + int messageSize, + String cipher, + ChannelType channelType, + PerfTestProtocol protocol) { + a_clientFactory = clientFactory; + b_serverFactory = serverFactory; + c_messageSize = messageSize; + d_cipher = cipher; + e_channelType = channelType; + f_protocol = protocol; } + public EndpointFactory clientFactory() { return a_clientFactory; } @@ -112,23 +112,43 @@ public final class ClientSocketPerfTest { for (EndpointFactory endpointFactory : EndpointFactory.values()) { for (ChannelType channelType : ChannelType.values()) { for (PerfTestProtocol protocol : PerfTestProtocol.values()) { - params.add(new Object[] {new Config(endpointFactory, - endpointFactory, 64, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - channelType, protocol)}); - params.add(new Object[] {new Config(endpointFactory, - endpointFactory, 512, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - channelType, protocol)}); - params.add(new Object[] {new Config(endpointFactory, - endpointFactory, 4096, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - channelType, protocol)}); + params.add( + new Object[] { + new Config( + endpointFactory, + endpointFactory, + 64, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + channelType, + protocol) + }); + params.add( + new Object[] { + new Config( + endpointFactory, + endpointFactory, + 512, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + channelType, + protocol) + }); + params.add( + new Object[] { + new Config( + endpointFactory, + endpointFactory, + 4096, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + channelType, + protocol) + }); } } } return params; } - private ClientEndpoint client; - private ServerEndpoint server; + private SocketPair socketPair = new SocketPair(); private byte[] message; private ExecutorService executor; private Future<?> sendingFuture; @@ -137,46 +157,78 @@ public final class ClientSocketPerfTest { private static final AtomicLong bytesCounter = new AtomicLong(); private AtomicBoolean recording = new AtomicBoolean(); + private static class SocketPair implements AutoCloseable { + public ClientEndpoint client; + public ServerEndpoint server; + + SocketPair() { + client = null; + server = null; + } + + @Override + public void close() { + if (client != null) { + client.stop(); + } + if (server != null) { + server.stop(); + } + } + } + private void setup(Config config) throws Exception { message = newTextMessage(512); // Always use the same server for consistency across the benchmarks. - server = config.serverFactory().newServer( - config.messageSize(), config.protocol().getProtocols(), - ciphers(config)); - - server.setMessageProcessor(new ServerEndpoint.MessageProcessor() { - @Override - public void processMessage(byte[] inMessage, int numBytes, OutputStream os) { - if (recording.get()) { - // Server received a message, increment the count. - bytesCounter.addAndGet(numBytes); - } - } - }); - Future<?> connectedFuture = server.start(); + socketPair.server = + config.serverFactory() + .newServer( + config.messageSize(), + config.protocol().getProtocols(), + ciphers(config)); + socketPair.server.init(); + + socketPair.server.setMessageProcessor( + new ServerEndpoint.MessageProcessor() { + @Override + public void processMessage(byte[] inMessage, int numBytes, OutputStream os) { + if (recording.get()) { + // Server received a message, increment the count. + bytesCounter.addAndGet(numBytes); + } + } + }); + Future<?> connectedFuture = socketPair.server.start(); - client = config.clientFactory().newClient( - config.channelType(), server.port(), config.protocol().getProtocols(), ciphers(config)); - client.start(); + socketPair.client = + config.clientFactory() + .newClient( + config.channelType(), + socketPair.server.port(), + config.protocol().getProtocols(), + ciphers(config)); + socketPair.client.start(); // Wait for the initial connection to complete. connectedFuture.get(5, TimeUnit.SECONDS); executor = Executors.newSingleThreadExecutor(); - sendingFuture = executor.submit(new Runnable() { - @Override - public void run() { - try { - Thread thread = Thread.currentThread(); - while (!stopping && !thread.isInterrupted()) { - client.sendMessage(message); - } - } finally { - client.flush(); - } - } - }); + sendingFuture = + executor.submit( + new Runnable() { + @Override + public void run() { + try { + Thread thread = Thread.currentThread(); + while (!stopping && !thread.isInterrupted()) { + socketPair.client.sendMessage(message); + } + } finally { + socketPair.client.flush(); + } + } + }); } void close() throws Exception { @@ -185,29 +237,37 @@ public final class ClientSocketPerfTest { // Wait for the sending thread to stop. sendingFuture.get(5, TimeUnit.SECONDS); - client.stop(); - server.stop(); - executor.shutdown(); - executor.awaitTermination(5, TimeUnit.SECONDS); + if (socketPair != null) { + socketPair.close(); + } + if (executor != null) { + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + } } - /** - * Simple benchmark for the amount of time to send a given number of messages - */ + /** Simple benchmark for the amount of time to send a given number of messages */ @Test @Parameters(method = "getParams") public void time(Config config) throws Exception { - reset(); - setup(config); - recording.set(true); - - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - while (bytesCounter.get() < config.messageSize()) { - } - bytesCounter.set(0); + try { + reset(); + setup(config); + recording.set(true); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + while (bytesCounter.get() < config.messageSize()) {} + bytesCounter.set(0); + } + recording.set(false); + } finally { + close(); } - recording.set(false); + } + + @After + public void tearDown() throws Exception { close(); } @@ -219,4 +279,4 @@ public final class ClientSocketPerfTest { private String[] ciphers(Config config) { return new String[] {config.cipher()}; } -}
\ No newline at end of file +} 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 1e4f12460936..83eaaa1155e4 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java @@ -16,10 +16,14 @@ package android.conscrypt; +import static org.conscrypt.TestUtils.getLoopbackAddress; + import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.AutoCloseable; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketException; import java.nio.channels.ClosedChannelException; @@ -37,7 +41,7 @@ import javax.net.ssl.SSLSocketFactory; /** * A simple socket-based test server. */ -final class ServerEndpoint { +final class ServerEndpoint implements AutoCloseable { /** * A processor for receipt of a single message. */ @@ -82,7 +86,11 @@ final class ServerEndpoint { this.messageSize = messageSize; this.protocols = protocols; this.cipherSuites = cipherSuites; - buffer = new byte[messageSize]; + this.buffer = new byte[messageSize]; + } + + void init() throws IOException { + serverSocket.bind(new InetSocketAddress(getLoopbackAddress(), 0)); } void setMessageProcessor(MessageProcessor messageProcessor) { @@ -94,6 +102,11 @@ final class ServerEndpoint { return executor.submit(new AcceptTask()); } + @Override + public void close() { + stop(); + } + void stop() { try { stopping = true; 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 af3c405eab82..90a87ce0c69d 100644 --- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java +++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java @@ -44,6 +44,7 @@ import junitparams.JUnitParamsRunner; import junitparams.Parameters; import org.junit.Rule; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -115,14 +116,33 @@ public final class ServerSocketPerfTest { return params; } - private ClientEndpoint client; - private ServerEndpoint server; + private SocketPair socketPair = new SocketPair(); private ExecutorService executor; private Future<?> receivingFuture; private volatile boolean stopping; private static final AtomicLong bytesCounter = new AtomicLong(); private AtomicBoolean recording = new AtomicBoolean(); + private static class SocketPair implements AutoCloseable { + public ClientEndpoint client; + public ServerEndpoint server; + + SocketPair() { + client = null; + server = null; + } + + @Override + public void close() { + if (client != null) { + client.stop(); + } + if (server != null) { + server.stop(); + } + } + } + private void setup(final Config config) throws Exception { recording.set(false); @@ -130,9 +150,10 @@ public final class ServerSocketPerfTest { final ChannelType channelType = config.channelType(); - server = config.serverFactory().newServer(config.messageSize(), + socketPair.server = config.serverFactory().newServer(config.messageSize(), new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config)); - server.setMessageProcessor(new MessageProcessor() { + socketPair.server.init(); + socketPair.server.setMessageProcessor(new MessageProcessor() { @Override public void processMessage(byte[] inMessage, int numBytes, OutputStream os) { try { @@ -151,20 +172,20 @@ public final class ServerSocketPerfTest { } }); - Future<?> connectedFuture = server.start(); + Future<?> connectedFuture = socketPair.server.start(); // Always use the same client for consistency across the benchmarks. - client = config.clientFactory().newClient( - ChannelType.CHANNEL, server.port(), + socketPair.client = config.clientFactory().newClient( + ChannelType.CHANNEL, socketPair.server.port(), new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config)); - client.start(); + socketPair.client.start(); // Wait for the initial connection to complete. connectedFuture.get(5, TimeUnit.SECONDS); // Start the server-side streaming by sending a message to the server. - client.sendMessage(message); - client.flush(); + socketPair.client.sendMessage(message); + socketPair.client.flush(); executor = Executors.newSingleThreadExecutor(); receivingFuture = executor.submit(new Runnable() { @@ -173,7 +194,7 @@ public final class ServerSocketPerfTest { Thread thread = Thread.currentThread(); byte[] buffer = new byte[config.messageSize()]; while (!stopping && !thread.isInterrupted()) { - int numBytes = client.readMessage(buffer); + int numBytes = socketPair.client.readMessage(buffer); if (numBytes < 0) { return; } @@ -191,25 +212,38 @@ public final class ServerSocketPerfTest { void close() throws Exception { stopping = true; // Stop and wait for sending to complete. - server.stop(); - client.stop(); - executor.shutdown(); - receivingFuture.get(5, TimeUnit.SECONDS); - executor.awaitTermination(5, TimeUnit.SECONDS); + if (socketPair != null) { + socketPair.close(); + } + if (executor != null) { + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + } + if (receivingFuture != null) { + receivingFuture.get(5, TimeUnit.SECONDS); + } } @Test @Parameters(method = "getParams") public void throughput(Config config) throws Exception { - setup(config); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - recording.set(true); - while (bytesCounter.get() < config.messageSize()) { - } - bytesCounter.set(0); - recording.set(false); + try { + setup(config); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + recording.set(true); + while (bytesCounter.get() < config.messageSize()) { + } + bytesCounter.set(0); + recording.set(false); + } + } finally { + close(); } + } + + @After + public void tearDown() throws Exception { close(); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8ffea237c71d..24594ab41100 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6204,7 +6204,7 @@ public class Notification implements Parcelable int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); - // Use different highlighted colors for conversations' unread count + // Use different highlighted colors for e.g. unopened groups if (p.mHighlightExpander) { pillColor = Colors.flattenAlpha( getColors(p).getTertiaryFixedDimAccentColor(), bgColor); @@ -6453,7 +6453,10 @@ public class Notification implements Parcelable big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); - if (Flags.notificationsRedesignTemplates()) { + // Update margins to leave space for the top line (but not for HUNs, which use a + // different layout that already accounts for that). + if (Flags.notificationsRedesignTemplates() + && p.mViewType != StandardTemplateParams.VIEW_TYPE_HEADS_UP) { int margin = getContentMarginTop(mContext, R.dimen.notification_2025_content_margin_top); big.setViewLayoutMargin(R.id.notification_main_column, RemoteViews.MARGIN_TOP, @@ -6801,6 +6804,8 @@ public class Notification implements Parcelable public RemoteViews makeNotificationGroupHeader() { return makeNotificationHeader(mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) + // Highlight group expander until the group is first opened + .highlightExpander(Flags.notificationsRedesignTemplates()) .fillTextsFrom(this)); } @@ -6817,6 +6822,12 @@ public class Notification implements Parcelable getHeaderLayoutResource()); resetNotificationHeader(header); bindNotificationHeader(header, p); + if (Flags.notificationsRedesignTemplates() + && (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED + || p.mViewType == StandardTemplateParams.VIEW_TYPE_PUBLIC)) { + // Center top line vertically in minimized and public header-only views + header.setBoolean(R.id.notification_header, "centerTopLine", true); + } return header; } @@ -6970,12 +6981,14 @@ public class Notification implements Parcelable * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise * a new subtext is created consisting of the content of the * notification. + * @param highlightExpander whether the expander should use the highlighted colors * @hide */ - public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { + public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext, + boolean highlightExpander) { StandardTemplateParams p = mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) - .highlightExpander(false) + .highlightExpander(highlightExpander) .fillTextsFrom(this); if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) { p.summaryText(createSummaryText()); diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 34c88e91a979..8da630c95135 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -28,6 +28,7 @@ import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag; import static com.android.hardware.input.Flags.mouseScrollingAcceleration; import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling; import static com.android.hardware.input.Flags.mouseSwapPrimaryButton; +import static com.android.hardware.input.Flags.pointerAcceleration; import static com.android.hardware.input.Flags.touchpadSystemGestureDisable; import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut; import static com.android.hardware.input.Flags.touchpadVisualizer; @@ -418,6 +419,15 @@ public class InputSettings { } /** + * Returns true if the feature flag for the pointer acceleration toggle is + * enabled. + * @hide + */ + public static boolean isPointerAccelerationFeatureFlagEnabled() { + return pointerAcceleration(); + } + + /** * Returns true if the touchpad visualizer is allowed to appear. * * @param context The application context. @@ -720,6 +730,47 @@ public class InputSettings { } /** + * Whether cursor acceleration is enabled or not for connected mice. + * + * @param context The application context. + * + * @hide + */ + public static boolean isMousePointerAccelerationEnabled(@NonNull Context context) { + if (!isPointerAccelerationFeatureFlagEnabled()) { + return false; + } + + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT) + == 1; + } + + /** + * Sets whether mouse acceleration is enabled. + * + * When enabled, the mouse cursor moves farther when it is moved faster. + * When disabled, the mouse cursor speed becomes directly proportional to + * the speed at which the mouse is moved. + * + * @param context The application context. + * @param enabled Will enable mouse acceleration if true, disable it if + * false. + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setMouseAccelerationEnabled(@NonNull Context context, + boolean enabled) { + if (!isPointerAccelerationFeatureFlagEnabled()) { + return; + } + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED, enabled ? 1 : 0, + UserHandle.USER_CURRENT); + } + + + /** * Whether Accessibility bounce keys feature is enabled. * * <p> diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index ed75491b8e21..ee62dea7f9e5 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -313,7 +313,10 @@ public class Binder implements IBinder { * If the current thread is not currently executing an incoming transaction, * then its own PID is returned. * - * Warning: oneway transactions do not receive PID. Even if you expect + * Warning do not use this as a security identifier! PID is unreliable + * as it may be re-used. This should mostly be used for debugging. + * + * oneway transactions do not receive PID. Even if you expect * a transaction to be synchronous, a misbehaving client could send it * as a asynchronous call and result in a 0 PID here. Additionally, if * there is a race and the calling process dies, the PID may still be diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2ad6669979b7..6e58780ac7a7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6362,6 +6362,16 @@ public final class Settings { public static final String MOUSE_SCROLLING_ACCELERATION = "mouse_scrolling_acceleration"; /** + * Whether mouse acceleration is enabled. + * + * When enabled, the mouse cursor will accelerate as the mouse moves faster. + * + * @hide + */ + public static final String MOUSE_POINTER_ACCELERATION_ENABLED = + "mouse_pointer_acceleration_enabled"; + + /** * Pointer fill style, specified by * {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants. * @@ -6610,6 +6620,7 @@ public final class Settings { PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING); PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON); + PRIVATE_SETTINGS.add(MOUSE_POINTER_ACCELERATION_ENABLED); PRIVATE_SETTINGS.add(PREFERRED_REGION); PRIVATE_SETTINGS.add(MOUSE_SCROLLING_ACCELERATION); } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 4f741982081b..880622a1e121 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -221,13 +221,13 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { @Override public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes, - int[] hideTypes, int[] cancelTypes) { + int[] hideTypes, int[] cancelTypes, int[] transientTypes) { if (Flags.refactorInsetsController()) { - return super.setControl(control, showTypes, hideTypes, cancelTypes); + return super.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } else { ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl", mController.getHost().getInputMethodManager(), null /* icProto */); - if (!super.setControl(control, showTypes, hideTypes, cancelTypes)) { + if (!super.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes)) { return false; } if (control == null && !mIsRequestedVisibleAwaitingLeash) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index b0813f3a98f6..c174fbe0bbcd 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -959,6 +959,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @InsetsType int[] showTypes = new int[1]; final @InsetsType int[] hideTypes = new int[1]; final @InsetsType int[] cancelTypes = new int[1]; + final @InsetsType int[] transientTypes = new int[1]; ImeTracker.Token statsToken = null; // Ensure to update all existing source consumers @@ -984,7 +985,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // control may be null, but we still need to update the control to null if it got // revoked. - consumer.setControl(control, showTypes, hideTypes, cancelTypes); + consumer.setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } // Ensure to create source consumers if not available yet. @@ -992,7 +993,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); getSourceConsumer(control.getId(), control.getType()) - .setControl(control, showTypes, hideTypes, cancelTypes); + .setControl(control, showTypes, hideTypes, cancelTypes, transientTypes); } } @@ -1020,10 +1021,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation handlePendingControlRequest(statsToken); } else { if (showTypes[0] != 0) { - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, + false /* skipsCallbacks */, statsToken); } if (hideTypes[0] != 0) { - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, + // The animation of hiding transient types shouldn't be detected by the + // app. Otherwise, it might be able to react to the callbacks and cause + // flickering. + (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */, + statsToken); } } } else { @@ -1033,7 +1040,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ImeTracker.TYPE_SHOW, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, mHost.isHandlingPointerEvent() /* fromUser */); - applyAnimation(showTypes[0], true /* show */, false /* fromIme */, newStatsToken); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, + false /* skipsCallbacks */, newStatsToken); } if (hideTypes[0] != 0) { final var newStatsToken = @@ -1041,7 +1049,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROLS_CHANGED, mHost.isHandlingPointerEvent() /* fromUser */); - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, newStatsToken); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, + // The animation of hiding transient types shouldn't be detected by the app. + // Otherwise, it might be able to react to the callbacks and cause + // flickering. + (hideTypes[0] & ~transientTypes[0]) == 0 /* skipsCallbacks */, + newStatsToken); } } @@ -1174,7 +1187,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // TODO(b/353463205) check if this is needed here ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication); } - applyAnimation(typesReady, true /* show */, fromIme, statsToken); + applyAnimation(typesReady, true /* show */, fromIme, false /* skipsCallbacks */, + statsToken); } /** @@ -1287,7 +1301,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation handlePendingControlRequest(statsToken); getImeSourceConsumer().removeSurface(); } - applyAnimation(typesReady, false /* show */, fromIme, statsToken); + applyAnimation(typesReady, false /* show */, fromIme, false /* skipsCallbacks */, + statsToken); } @Override @@ -2007,24 +2022,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, - @Nullable ImeTracker.Token statsToken) { + boolean skipsCallbacks, @Nullable ImeTracker.Token statsToken) { // TODO(b/166736352): We should only skip the animation of specific types, not all types. - boolean skipAnim = false; + boolean skipsAnim = false; if ((types & ime()) != 0) { final InsetsSourceControl imeControl = mImeSourceConsumer.getControl(); // Skip showing animation once that made by system for some reason. // (e.g. starting window with IME snapshot) if (imeControl != null) { - skipAnim = imeControl.getAndClearSkipAnimationOnce() && show + skipsAnim = imeControl.getAndClearSkipAnimationOnce() && show && mImeSourceConsumer.hasViewFocusWhenWindowFocusGain(); } } - applyAnimation(types, show, fromIme, skipAnim, statsToken); + applyAnimation(types, show, fromIme, skipsAnim, skipsCallbacks, statsToken); } @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, - boolean skipAnim, @Nullable ImeTracker.Token statsToken) { + boolean skipsAnim, boolean skipsCallbacks, @Nullable ImeTracker.Token statsToken) { if (types == 0) { // nothing to animate. if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate. Stopping here"); @@ -2040,7 +2055,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); final InternalAnimationControlListener listener = new InternalAnimationControlListener( show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(), - skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), + skipsAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), mLoggingListener, mJankContext); // We are about to playing the default animation (show/hide). Passing a null frame indicates @@ -2050,7 +2065,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener /* insetsAnimationSpec */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken, + !hasAnimationCallbacks || skipsCallbacks /* useInsetsAnimationThread */, statsToken, false /* fromPredictiveBack */); } @@ -2173,12 +2188,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation new InsetsSourceControl(ID_IME_CAPTION_BAR, captionBar(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); } else { mState.removeSource(ID_IME_CAPTION_BAR); InsetsSourceConsumer sourceConsumer = mSourceConsumers.get(ID_IME_CAPTION_BAR); if (sourceConsumer != null) { - sourceConsumer.setControl(null, new int[1], new int[1], new int[1]); + sourceConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); } } mHost.notifyInsetsChanged(); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 17f33c1af4ed..e8e66210bca6 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -130,7 +130,10 @@ public class InsetsSourceConsumer { * @return Whether the control has changed from the server */ public boolean setControl(@Nullable InsetsSourceControl control, - @InsetsType int[] showTypes, @InsetsType int[] hideTypes, int[] cancelTypes) { + @InsetsType int[] showTypes, + @InsetsType int[] hideTypes, + @InsetsType int[] cancelTypes, + @InsetsType int[] transientTypes) { if (Objects.equals(mSourceControl, control)) { if (mSourceControl != null && mSourceControl != control) { mSourceControl.release(SurfaceControl::release); @@ -185,6 +188,9 @@ public class InsetsSourceConsumer { } else { hideTypes[0] |= mType; } + if (lastControl != null && lastControl.isFake()) { + transientTypes[0] |= mType; + } } else { // We are gaining control, but don't need to run an animation. // However make sure that the leash visibility is still up to date. diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index acbd95bf6810..7f2f0e8863df 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -142,6 +142,10 @@ public class InsetsSourceControl implements Parcelable { return mInsetsHint; } + public boolean isFake() { + return mLeash == null && Insets.NONE.equals(mInsetsHint); + } + public void setSkipAnimationOnce(boolean skipAnimation) { mSkipAnimationOnce = skipAnimation; } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 42b798c8f939..3a0e6f187986 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -16,6 +16,10 @@ package android.view; +import static android.app.Flags.notificationsRedesignTemplates; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -27,6 +31,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; +import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -174,6 +179,33 @@ public class NotificationHeaderView extends RelativeLayout { } /** + * Center top line and expand button vertically. + */ + @RemotableViewMethod + public void centerTopLine(boolean center) { + if (notificationsRedesignTemplates()) { + // The content of the top line view is already center-aligned, but since the height + // matches the content by default, it looks top-aligned. If the height matches the + // parent instead, the text ends up correctly centered in the parent. + ViewGroup.LayoutParams lp = mTopLineView.getLayoutParams(); + lp.height = center ? MATCH_PARENT : WRAP_CONTENT; + mTopLineView.setLayoutParams(lp); + + centerExpandButton(center); + } + } + + /** Center expand button vertically. */ + private void centerExpandButton(boolean center) { + ViewGroup.LayoutParams lp = mExpandButton.getLayoutParams(); + lp.height = center ? MATCH_PARENT : WRAP_CONTENT; + if (lp instanceof FrameLayout.LayoutParams flp) { + flp.gravity = center ? Gravity.CENTER : (Gravity.TOP | Gravity.END); + } + mExpandButton.setLayoutParams(lp); + } + + /** * This is used to make the low-priority header show the bolded text of a title. * * @param styleTextAsTitle true if this header's text is to have the style of a title diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java index a2919f5bfd74..e567414c9b8a 100644 --- a/core/java/android/view/NotificationTopLineView.java +++ b/core/java/android/view/NotificationTopLineView.java @@ -16,6 +16,8 @@ package android.view; +import static android.app.Flags.notificationsRedesignTemplates; + import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; @@ -112,7 +114,9 @@ public class NotificationTopLineView extends ViewGroup { final int givenHeight = MeasureSpec.getSize(heightMeasureSpec); final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth, MeasureSpec.AT_MOST); - int heightSpec = MeasureSpec.makeMeasureSpec(givenHeight, MeasureSpec.AT_MOST); + int heightSpec = notificationsRedesignTemplates() + ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + : MeasureSpec.makeMeasureSpec(givenHeight, MeasureSpec.AT_MOST); int totalWidth = getPaddingStart(); int maxChildHeight = -1; mMaxAscent = -1; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 16cdb64f62cc..8ef0b0eebb8c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -10527,28 +10527,7 @@ public final class ViewRootImpl implements ViewParent, @Override public void onInputEvent(InputEvent event) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility"); - List<InputEvent> processedEvents; - try { - processedEvents = - mInputCompatProcessor.processInputEventForCompatibility(event); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - if (processedEvents != null) { - if (processedEvents.isEmpty()) { - // InputEvent consumed by mInputCompatProcessor - finishInputEvent(event, true); - } else { - for (int i = 0; i < processedEvents.size(); i++) { - enqueueInputEvent( - processedEvents.get(i), this, - QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true); - } - } - } else { - enqueueInputEvent(event, this, 0, true); - } + processRawInputEvent(event); } @Override @@ -10758,6 +10737,42 @@ public final class ViewRootImpl implements ViewParent, final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable = new InvalidateOnAnimationRunnable(); + /** + * Handle the incoming event. + * + * <p>The event will be first sent to the compatibility processor, which could choose to handle + * it. The compat processor could also choose to produce more synthetic events in response to + * the incoming one. Events that are not consumed by the compat processor are added to the + * {@link ViewRootImpl}'s queue for further processing inside ViewRootImpl. + * + * @hide + */ + @VisibleForTesting + public void processRawInputEvent(InputEvent event) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility"); + List<InputEvent> processedEvents; + try { + processedEvents = + mInputCompatProcessor.processInputEventForCompatibility(event); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + if (processedEvents != null) { + if (processedEvents.isEmpty()) { + // InputEvent consumed by mInputCompatProcessor + mInputEventReceiver.finishInputEvent(event, true); + } else { + for (int i = 0; i < processedEvents.size(); i++) { + enqueueInputEvent( + processedEvents.get(i), mInputEventReceiver, + QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true); + } + } + } else { + enqueueInputEvent(event, mInputEventReceiver, 0, true); + } + } + public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 4b7bacb23e5d..924bdbf71e78 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -282,20 +282,6 @@ public final class BackNavigationInfo implements Parcelable { } /** - * Callback to be called when the back gesture is finished in order to notify the server that - * it can ask app to start rendering. - * @hide - * @param triggerBack Boolean indicating if back gesture has been triggered. - */ - public void onBackGestureFinished(boolean triggerBack) { - if (mOnBackNavigationDone != null) { - Bundle result = new Bundle(); - result.putBoolean(KEY_GESTURE_FINISHED, triggerBack); - mOnBackNavigationDone.sendResult(result); - } - } - - /** * Get customize animation info. * @hide */ diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7eabd1733c5b..74a3819e03f9 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -292,17 +292,6 @@ flag { } flag { - name: "migrate_predictive_back_transition" - namespace: "windowing_frontend" - description: "Create transition when visibility change from predictive back" - bug: "347168362" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "always_capture_activity_snapshot" namespace: "windowing_frontend" description: "Always capture activity snapshot regardless predictive back status" diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index 751cfde70164..80bc4fd89c8d 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -27,12 +27,12 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.view.RemotableViewMethod; -import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -49,12 +49,15 @@ public class NotificationExpandButton extends FrameLayout { private Drawable mPillDrawable; private TextView mNumberView; private ImageView mIconView; + private LinearLayout mPillView; private boolean mExpanded; private int mNumber; private int mDefaultPillColor; private int mDefaultTextColor; private int mHighlightPillColor; private int mHighlightTextColor; + // Track whether this ever had mExpanded = true, so that we don't highlight it anymore. + private boolean mWasExpanded = false; public NotificationExpandButton(Context context) { this(context, null, 0, 0); @@ -78,8 +81,8 @@ public class NotificationExpandButton extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); - final View pillView = findViewById(R.id.expand_button_pill); - final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground(); + mPillView = findViewById(R.id.expand_button_pill); + final LayerDrawable layeredPill = (LayerDrawable) mPillView.getBackground(); mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer); mNumberView = findViewById(R.id.expand_button_number); mIconView = findViewById(R.id.expand_button_icon); @@ -133,6 +136,7 @@ public class NotificationExpandButton extends FrameLayout { int contentDescriptionId; if (mExpanded) { if (notificationsRedesignTemplates()) { + mWasExpanded = true; drawableId = R.drawable.ic_notification_2025_collapse; } else { drawableId = R.drawable.ic_collapse_notification; @@ -149,8 +153,12 @@ public class NotificationExpandButton extends FrameLayout { setContentDescription(mContext.getText(contentDescriptionId)); mIconView.setImageDrawable(getContext().getDrawable(drawableId)); - // changing the expanded state can affect the number display - updateNumber(); + if (!notificationsRedesignTemplates()) { + // changing the expanded state can affect the number display + updateNumber(); + } else { + updateColors(); + } } private void updateNumber() { @@ -164,31 +172,76 @@ public class NotificationExpandButton extends FrameLayout { mNumberView.setVisibility(GONE); } - // changing number can affect the color + // changing number can affect the color and padding updateColors(); + updatePadding(); + } + + private void updatePadding() { + if (!notificationsRedesignTemplates()) { + return; + } + + // Reduce the padding at the end when showing the number, since the arrow icon has more + // inherent spacing than the number does. This makes the content look more centered. + // Vertical padding remains unchanged. + int reducedPadding = getResources().getDimensionPixelSize( + R.dimen.notification_2025_expand_button_reduced_end_padding); + int normalPadding = getResources().getDimensionPixelSize( + R.dimen.notification_2025_expand_button_horizontal_icon_padding); + mPillView.setPaddingRelative( + /* start = */ normalPadding, + /* top = */ mPillView.getPaddingTop(), + /* end = */ shouldShowNumber() ? reducedPadding : normalPadding, + /* bottom = */ mPillView.getPaddingBottom() + ); + } + + /** + * Use highlight colors for the expander for groups (when the number is showing) that haven't + * been opened before, as long as the colors are available. + */ + private boolean shouldBeHighlighted() { + return !mWasExpanded && shouldShowNumber() + && mHighlightPillColor != 0 && mHighlightTextColor != 0; } private void updateColors() { - if (shouldShowNumber()) { - if (mHighlightPillColor != 0) { + if (notificationsRedesignTemplates()) { + if (shouldBeHighlighted()) { mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor)); - } - mIconView.setColorFilter(mHighlightTextColor); - if (mHighlightTextColor != 0) { + mIconView.setColorFilter(mHighlightTextColor); mNumberView.setTextColor(mHighlightTextColor); - } - } else { - if (mDefaultPillColor != 0) { + } else { mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor)); - } - mIconView.setColorFilter(mDefaultTextColor); - if (mDefaultTextColor != 0) { + mIconView.setColorFilter(mDefaultTextColor); mNumberView.setTextColor(mDefaultTextColor); } + } else { + if (shouldShowNumber()) { + if (mHighlightPillColor != 0) { + mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor)); + } + mIconView.setColorFilter(mHighlightTextColor); + if (mHighlightTextColor != 0) { + mNumberView.setTextColor(mHighlightTextColor); + } + } else { + if (mDefaultPillColor != 0) { + mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor)); + } + mIconView.setColorFilter(mDefaultTextColor); + if (mDefaultTextColor != 0) { + mNumberView.setTextColor(mDefaultTextColor); + } + } } } private boolean shouldShowNumber() { + if (notificationsRedesignTemplates()) { + return mNumber > 1; + } return !mExpanded && mNumber > 1; } @@ -230,7 +283,7 @@ public class NotificationExpandButton extends FrameLayout { /** * Sets the number shown inside the expand button. - * This only appears when the expand button is collapsed, and when greater than 1. + * This only appears when {@link this#shouldShowNumber()} is true. */ @RemotableViewMethod public void setNumber(int number) { diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index dd9bfa51c634..0d99200f4e6f 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -228,6 +228,7 @@ message SystemSettingsProto { optional SettingProto reverse_vertical_scrolling = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto scrolling_acceleration = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto pointer_acceleration_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Mouse mouse = 38; diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid.xml b/core/res/res/drawable/ic_accessibility_hearing_aid.xml index e5ffeb0274fc..79c61a64302d 100644 --- a/core/res/res/drawable/ic_accessibility_hearing_aid.xml +++ b/core/res/res/drawable/ic_accessibility_hearing_aid.xml @@ -17,8 +17,10 @@ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@color/accessibility_feature_background" /> <foreground> + <!-- TODO: b/382377298 - To update correct drawable with same inset + @dimen/accessibility_icon_foreground_padding_ratio or remove the inset together. --> <inset android:drawable="@drawable/ic_accessibility_hearing_aid_foreground" - android:inset="@dimen/accessibility_icon_foreground_padding_ratio" /> + android:inset="30%" /> </foreground> </adaptive-icon> diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml new file mode 100644 index 000000000000..4f0036ccd8d2 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14" + android:viewportHeight="14"> + <path + android:pathData="M7,7m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0" + android:strokeWidth="2" + android:fillColor="#3998D3" + android:strokeColor="#3E373C"/> +</vector> diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml new file mode 100644 index 000000000000..2b302e18ad37 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml @@ -0,0 +1,26 @@ +<!-- + 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. +--> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/accessibility_feature_background" /> + <foreground> + <!-- TODO: b/382377298 - To update correct drawable with same inset + @dimen/accessibility_icon_foreground_padding_ratio or remove the inset together. --> + <inset + android:drawable="@drawable/ic_accessibility_hearing_aid_disconnected_foreground" + android:inset="30%" /> + </foreground> +</adaptive-icon> diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml new file mode 100644 index 000000000000..1097ed9067d7 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml @@ -0,0 +1,26 @@ +<!-- + 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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="22dp" + android:height="23dp" + android:viewportWidth="22" + android:viewportHeight="23"> + <path + android:pathData="M13,1C14.275,1 15.42,1.284 16.435,1.851L14.934,3.352C14.346,3.117 13.701,3 13,3C11.583,3 10.396,3.479 9.438,4.438C8.479,5.396 8,6.583 8,8C8,8.671 8.109,9.324 8.326,9.96L6.794,11.492C6.265,10.355 6,9.191 6,8C6,6.033 6.675,4.375 8.025,3.025C9.375,1.675 11.033,1 13,1ZM13,4.5C13.251,4.5 13.491,4.522 13.72,4.566L9.566,8.72C9.522,8.49 9.5,8.25 9.5,8C9.5,7.033 9.842,6.208 10.525,5.525C11.208,4.842 12.033,4.5 13,4.5ZM9.332,14.582L1.707,22.207L0.293,20.793L20.293,0.793L21.707,2.207L19.22,4.694C19.74,5.677 20,6.779 20,8H18C18,7.356 17.901,6.76 17.703,6.211L16.465,7.449C16.514,7.793 16.509,8.152 16.45,8.525L20.575,12.1L19.075,13.3C19.408,13.833 19.646,14.421 19.788,15.063C19.929,15.704 20,16.35 20,17C20,18.1 19.608,19.042 18.825,19.825C18.042,20.608 17.1,21 16,21C15.05,21 14.217,20.717 13.5,20.15C12.783,19.583 12.267,18.833 11.95,17.9C11.667,17.067 11.363,16.45 11.038,16.05C10.712,15.65 10.175,15.183 9.425,14.65C9.394,14.628 9.363,14.605 9.332,14.582ZM10.756,13.158C11.518,13.704 12.129,14.23 12.587,14.738C13.063,15.262 13.442,15.975 13.725,16.875C13.908,17.458 14.188,17.958 14.563,18.375C14.938,18.792 15.417,19 16,19C16.533,19 17,18.804 17.4,18.413C17.8,18.021 18,17.55 18,17C18,16.567 17.958,16.138 17.875,15.712C17.792,15.288 17.658,14.9 17.475,14.55L15.775,15.9L13.275,11.5C12.985,11.524 12.706,11.516 12.438,11.477L10.756,13.158ZM3.989,14.297L5.434,12.852C5.145,12.405 4.896,11.934 4.688,11.438C4.229,10.346 4,9.2 4,8C4,6.783 4.229,5.625 4.688,4.525C5.146,3.425 5.8,2.45 6.65,1.6L5.2,0.2C4.167,1.25 3.375,2.446 2.825,3.788C2.275,5.129 2,6.533 2,8C2,9.433 2.275,10.821 2.825,12.163C3.136,12.921 3.524,13.632 3.989,14.297ZM16.375,12.9L17.425,12.05L15.55,10.4L15.363,10.587C15.304,10.646 15.242,10.7 15.175,10.75L16.375,12.9Z" + android:fillColor="#ffffff" + android:fillType="evenOdd"/> +</vector> diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml new file mode 100644 index 000000000000..e8f0063bb4a8 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14" + android:viewportHeight="14"> + <path + android:pathData="M7,7m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0" + android:strokeWidth="2" + android:fillColor="#6DD58C" + android:strokeColor="#3E373C"/> +</vector> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index a4735fe6c7af..2adb79118ed9 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -408,6 +408,9 @@ <!-- the padding of the expand icon in the notification header --> <dimen name="notification_2025_expand_button_horizontal_icon_padding">6dp</dimen> + <!-- a smaller padding for the end of the expand button, for use when showing the number --> + <dimen name="notification_2025_expand_button_reduced_end_padding">4dp</dimen> + <!-- the size of the notification close button --> <dimen name="notification_close_button_size">16dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 33d3858562af..6313054e47f5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4947,6 +4947,18 @@ <!-- Title of hearing aids feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] --> <string name="hearing_aids_feature_name">Hearing devices</string> + <!-- Text of hearing device disconnected state. [CHAR LIMIT=20] --> + <string name="hearing_device_status_disconnected">Disconnected</string> + + <!-- Text of hearing device connected state. [CHAR LIMIT=20] --> + <string name="hearing_device_status_connected">Connected</string> + + <!-- Text of hearing device active state. Active means device is currently connected and able to streaming the sound. [CHAR LIMIT=20] --> + <string name="hearing_device_status_active">Active</string> + + <!-- Text of hearing device loading state. Loading means device is not available yet, it might be in connecting or disconnecting. [CHAR LIMIT=20] --> + <string name="hearing_device_status_loading">Loading</string> + <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4789624685c2..a18f923d625b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3247,6 +3247,8 @@ <java-symbol type="dimen" name="notification_content_margin" /> <java-symbol type="dimen" name="notification_2025_margin" /> <java-symbol type="dimen" name="notification_2025_content_margin_top" /> + <java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" /> + <java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" /> <java-symbol type="dimen" name="notification_progress_margin_horizontal" /> <java-symbol type="dimen" name="notification_header_background_height" /> <java-symbol type="dimen" name="notification_header_touchable_height" /> @@ -3821,10 +3823,18 @@ <java-symbol type="drawable" name="ic_accessibility_color_correction" /> <java-symbol type="drawable" name="ic_accessibility_generic" /> <java-symbol type="drawable" name="ic_accessibility_hearing_aid" /> + <java-symbol type="drawable" name="ic_accessibility_hearing_aid_disconnected" /> + <java-symbol type="drawable" name="ic_accessibility_hearing_aid_green_dot" /> + <java-symbol type="drawable" name="ic_accessibility_hearing_aid_blue_dot" /> <java-symbol type="drawable" name="ic_accessibility_magnification" /> <java-symbol type="drawable" name="ic_accessibility_reduce_bright_colors" /> <java-symbol type="drawable" name="ic_accessibility_one_handed" /> + <java-symbol type="string" name="hearing_device_status_disconnected" /> + <java-symbol type="string" name="hearing_device_status_connected" /> + <java-symbol type="string" name="hearing_device_status_active" /> + <java-symbol type="string" name="hearing_device_status_loading" /> + <java-symbol type="string" name="hearing_aids_feature_name" /> <java-symbol type="string" name="reduce_bright_colors_feature_name" /> <java-symbol type="string" name="one_handed_mode_feature_name" /> diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 248db65d7435..4a54f6b12c9d 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -133,7 +133,7 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(false) /* skipsCallbacks */, eq(statsToken)); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -142,10 +142,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); }); } @@ -163,7 +163,7 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(false) /* skipsCallbacks */, eq(statsToken)); // Clear previous invocations to verify this is never called with control without leash. clearInvocations(mController); @@ -175,10 +175,10 @@ public class ImeInsetsSourceConsumerTest { // as we have no leash. verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); // set control with leash and verify visibility is applied. InsetsSourceControl controlWithLeash = new InsetsSourceControl(ID_IME, @@ -187,10 +187,10 @@ public class ImeInsetsSourceConsumerTest { // IME show animation should be triggered when control with leash becomes available. verify(mController).applyAnimation( eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, - and(not(eq(statsToken)), notNull())); + eq(false) /* skipsCallbacks */, and(not(eq(statsToken)), notNull())); }); } @@ -223,7 +223,8 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsToken)); + eq(false) /* skipsAnim */, eq(false) /* skipsCallbacks */, + eq(statsToken)); } // set control and verify visibility is applied. @@ -241,7 +242,8 @@ public class ImeInsetsSourceConsumerTest { // so the statsToken won't match. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(expectSkipAnim) /* skipAnim */, and(not(eq(statsToken)), notNull())); + eq(expectSkipAnim) /* skipsAnim */, eq(false) /* skipsCallbacks */, + and(not(eq(statsToken)), notNull())); } // If previously hasViewFocus is false, verify when requesting the IME visible next @@ -252,14 +254,16 @@ public class ImeInsetsSourceConsumerTest { // Called once through the show flow. verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsTokenNext)); + eq(false) /* skipsAnim */, eq(false) /* skipsCallbacks */, + eq(statsTokenNext)); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull())); + eq(true) /* skipsAnim */, eq(false) /* skipsCallbacks */, + and(not(eq(statsToken)), notNull())); } }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index d7f6a29d7c86..905d897e9ab0 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -100,14 +100,14 @@ public class InsetsAnimationControlImplTest { topConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(), mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mInsetsState, mMockController); navConsumer.setControl( new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(), mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars()); navConsumer.applyLocalVisibilityOverride(); diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 3a8f7ee3d7c8..45d66e8ee3a9 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), - new int[1], new int[1], new int[1]); + new int[1], new int[1], new int[1], new int[1]); } @Test @@ -129,7 +129,7 @@ public class InsetsSourceConsumerTest { newControl.setInsetsHint(Insets.of(0, 0, 0, 100)); int[] cancelTypes = {0}; - mConsumer.setControl(newControl, new int[1], new int[1], cancelTypes); + mConsumer.setControl(newControl, new int[1], new int[1], cancelTypes, new int[1]); assertEquals(statusBars(), cancelTypes[0]); }); @@ -196,7 +196,7 @@ public class InsetsSourceConsumerTest { @Test public void testRestore() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - mConsumer.setControl(null, new int[1], new int[1], new int[1]); + mConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); mSurfaceParamsApplied = false; mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); assertFalse(mSurfaceParamsApplied); @@ -204,7 +204,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), - new int[1], hideTypes, new int[1]); + new int[1], hideTypes, new int[1], new int[1]); assertEquals(statusBars(), hideTypes[0]); assertFalse(mRemoveSurfaceCalled); }); @@ -214,7 +214,7 @@ public class InsetsSourceConsumerTest { public void testRestore_noAnimation() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars()); - mConsumer.setControl(null, new int[1], new int[1], new int[1]); + mConsumer.setControl(null, new int[1], new int[1], new int[1], new int[1]); mLeash = new SurfaceControl.Builder(mSession) .setName("testSurface") .build(); @@ -223,7 +223,7 @@ public class InsetsSourceConsumerTest { mConsumer.setControl( new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), - new int[1], hideTypes, new int[1]); + new int[1], hideTypes, new int[1], new int[1]); assertTrue(mRemoveSurfaceCalled); assertEquals(0, hideTypes[0]); }); @@ -252,7 +252,7 @@ public class InsetsSourceConsumerTest { // Initial IME insets source control with its leash. imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1], - new int[1]); + new int[1], new int[1]); mSurfaceParamsApplied = false; // Verify when the app requests controlling show IME animation, the IME leash @@ -262,7 +262,7 @@ public class InsetsSourceConsumerTest { assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime())); imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash, true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1], - new int[1]); + new int[1], new int[1]); assertFalse(mSurfaceParamsApplied); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index ddc107e0dbc4..77891297a1bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -28,7 +28,6 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.window.flags.Flags.migratePredictiveBackTransition; import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.window.flags.Flags.unifyBackNavigationTransition; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; @@ -215,9 +214,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); setTriggerBack(false); // Trigger close transition if necessary. - if (Flags.migratePredictiveBackTransition()) { - mBackTransitionHandler.onAnimationFinished(); - } + mBackTransitionHandler.onAnimationFinished(); resetTouchTracker(); // Don't wait for animation start mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); @@ -918,23 +915,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); // The next callback should be {@link #onBackAnimationFinished}. - final boolean migrateBackToTransition = migratePredictiveBackTransition(); if (mCurrentTracker.getTriggerBack()) { - if (migrateBackToTransition) { - // notify core gesture is commit - if (shouldTriggerCloseTransition()) { - mBackTransitionHandler.mCloseTransitionRequested = true; - final IOnBackInvokedCallback callback = - mBackNavigationInfo.getOnBackInvokedCallback(); - // invoked client side onBackInvoked - dispatchOnBackInvoked(callback); - mRealCallbackInvoked = true; - } - } else { - // notify gesture finished - mBackNavigationInfo.onBackGestureFinished(true); + // notify core gesture is commit + if (shouldTriggerCloseTransition()) { + mBackTransitionHandler.mCloseTransitionRequested = true; + final IOnBackInvokedCallback callback = + mBackNavigationInfo.getOnBackInvokedCallback(); + // invoked client side onBackInvoked + dispatchOnBackInvoked(callback); + mRealCallbackInvoked = true; } - // start post animation dispatchOnBackInvoked(mActiveCallback); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 47032fd8616f..13f8e9ef9dd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -222,15 +222,9 @@ public class BubbleExpandedView extends LinearLayout { mTaskView.getBoundsOnScreen(launchBounds); options.setTaskAlwaysOnTop(true); - options.setLaunchedFromBubble(true); options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); @@ -242,7 +236,6 @@ public class BubbleExpandedView extends LinearLayout { context, /* requestCode= */ 0, mBubble.getAppBubbleIntent() - .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); @@ -250,13 +243,19 @@ public class BubbleExpandedView extends LinearLayout { launchBounds); } else if (!mIsOverflow && isShortcutBubble) { ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey()); + options.setLaunchedFromBubble(true); options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { + options.setLaunchedFromBubble(true); if (mBubble != null) { mBubble.setIntentActive(); } + final Intent fillInIntent = new Intent(); + // Apply flags to make behaviour match documentLaunchMode=always. + fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); mTaskView.startActivity(mPendingIntent, fillInIntent, options, launchBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 0c0fd7b10f6e..89c038b4a26b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -105,15 +105,8 @@ public class BubbleTaskViewHelper { getBubbleKey()); try { options.setTaskAlwaysOnTop(true); - options.setLaunchedFromBubble(true); options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); - - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); if (mBubble.isAppBubble()) { @@ -124,20 +117,25 @@ public class BubbleTaskViewHelper { context, /* requestCode= */ 0, mBubble.getAppBubbleIntent() - .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); } else if (isShortcutBubble) { + options.setLaunchedFromBubble(true); options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); } else { + options.setLaunchedFromBubble(true); if (mBubble != null) { mBubble.setIntentActive(); } + final Intent fillInIntent = new Intent(); + // Apply flags to make behaviour match documentLaunchMode=always. + fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); mTaskView.startActivity(mPendingIntent, fillInIntent, options, launchBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 94a6e5862b6c..03f388c9f1c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -81,11 +81,12 @@ public abstract class Pip2Module { @NonNull PipScheduler pipScheduler, @NonNull PipTransitionState pipStackListenerController, @NonNull PipDisplayLayoutState pipDisplayLayoutState, - @NonNull PipUiStateChangeController pipUiStateChangeController) { + @NonNull PipUiStateChangeController pipUiStateChangeController, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener, pipScheduler, pipStackListenerController, pipDisplayLayoutState, - pipUiStateChangeController); + pipUiStateChangeController, desktopUserRepositoriesOptional); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 2e38449d4584..8061ee9090b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -63,6 +63,7 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; @@ -72,6 +73,8 @@ import com.android.wm.shell.shared.pip.PipContentOverlay; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import java.util.Optional; + /** * Implementation of transitions for PiP on phone. */ @@ -106,6 +109,7 @@ public class PipTransition extends PipTransitionController implements private final PipScheduler mPipScheduler; private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; + private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; // // Transition caches @@ -140,7 +144,8 @@ public class PipTransition extends PipTransitionController implements PipScheduler pipScheduler, PipTransitionState pipTransitionState, PipDisplayLayoutState pipDisplayLayoutState, - PipUiStateChangeController pipUiStateChangeController) { + PipUiStateChangeController pipUiStateChangeController, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); @@ -151,6 +156,7 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDisplayLayoutState = pipDisplayLayoutState; + mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; } @Override @@ -820,6 +826,17 @@ public class PipTransition extends PipTransitionController implements return false; } + + // Since opening a new task while in Desktop Mode always first open in Fullscreen + // until DesktopMode Shell code resolves it to Freeform, PipTransition will get a + // possibility to handle it also. In this case return false to not have it enter PiP. + final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty() + && mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( + pipTask.displayId) > 0; + if (isInDesktopSession) { + return false; + } + // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type // implies that we are entering PiP in button navigation mode. This is guaranteed by // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index b5220c4ffb50..1917996d48fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -30,7 +30,6 @@ import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; -import com.android.window.flags.Flags; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; @@ -73,19 +72,12 @@ public class HomeTransitionObserver implements TransitionObserver, final int mode = change.getMode(); final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED); if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { - if (Flags.migratePredictiveBackTransition()) { - final boolean gestureToHomeTransition = isBackGesture - && TransitionUtil.isClosingType(info.getType()); - if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode) - || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) { - notifyHomeVisibilityChanged(gestureToHomeTransition - || TransitionUtil.isOpeningType(mode)); - } - } else { - if (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture) { - notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) - || isBackGesture); - } + final boolean gestureToHomeTransition = isBackGesture + && TransitionUtil.isClosingType(info.getType()); + if (gestureToHomeTransition || TransitionUtil.isClosingMode(mode) + || (!isBackGesture && TransitionUtil.isOpeningMode(mode))) { + notifyHomeVisibilityChanged(gestureToHomeTransition + || TransitionUtil.isOpeningType(mode)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index edb2e1cbcca6..d5929f010e02 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -30,7 +30,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; -import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; @@ -40,7 +39,6 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; -import static com.android.window.flags.Flags.migratePredictiveBackTransition; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -848,12 +846,6 @@ public class Transitions implements RemoteCallable<Transitions>, info.getChanges().remove(i); continue; } - // The change has already animated by back gesture, don't need to play transition - // animation on it. - if (!migratePredictiveBackTransition() - && change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) { - info.getChanges().remove(i); - } } // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 8bdefa7cf866..3e53ee5cfb9f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -39,8 +39,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.SurfaceControl; @@ -51,7 +49,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; @@ -197,27 +194,6 @@ public class HomeTransitionObserverTest extends ShellTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION) - public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException { - TransitionInfo info = mock(TransitionInfo.class); - TransitionInfo.Change change = mock(TransitionInfo.Change.class); - ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); - when(change.getTaskInfo()).thenReturn(taskInfo); - when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); - - when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true); - setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE, true); - - mHomeTransitionObserver.onTransitionReady(mock(IBinder.class), - info, - mock(SurfaceControl.Transaction.class), - mock(SurfaceControl.Transaction.class)); - - verify(mListener, times(1)).onHomeVisibilityChanged(true); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION) public void testHomeActivityWithBackGestureNotifiesHomeIsVisibleAfterClose() throws RemoteException { TransitionInfo info = mock(TransitionInfo.class); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 245360c925ad..3738312b762f 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -1386,14 +1386,21 @@ public final class MediaRouter2 { "requestCreateSessionByManager | requestId: %d, oldSession: %s, route: %s", managerRequestId, oldSession, route)); RoutingController controller; + String oldSessionId = oldSession.getId(); if (oldSession.isSystemSession()) { controller = getSystemController(); } else { synchronized (mLock) { - controller = mNonSystemRoutingControllers.get(oldSession.getId()); + controller = mNonSystemRoutingControllers.get(oldSessionId); } } if (controller == null) { + Log.w( + TAG, + TextUtils.formatSimple( + "Ignoring requestCreateSessionByManager (requestId: %d) because no" + + " controller for old session (id: %s) was found.", + managerRequestId, oldSessionId)); return; } requestCreateController(controller, route, managerRequestId); diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 7e87462b64de..166b388d9b7b 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -276,16 +276,19 @@ public final class MediaQualityManager { /** * Sets preferred default picture profile. * - * @param id the ID of the default profile. {@code null} to unset the default profile. + * @param pictureProfileId the ID of the default profile. {@code null} to unset the default + * profile. * @return {@code true} if it's set successfully; {@code false} otherwise. * + * @see PictureProfile#getProfileId() + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) - public boolean setDefaultPictureProfile(@Nullable String id) { + public boolean setDefaultPictureProfile(@Nullable String pictureProfileId) { try { - return mService.setDefaultPictureProfile(id, mUserHandle); + return mService.setDefaultPictureProfile(pictureProfileId, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -467,16 +470,19 @@ public final class MediaQualityManager { /** * Sets preferred default sound profile. * - * @param id the ID of the default profile. {@code null} to unset the default profile. + * @param soundProfileId the ID of the default profile. {@code null} to unset the default + * profile. * @return {@code true} if it's set successfully; {@code false} otherwise. * + * @see SoundProfile#getProfileId() + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) - public boolean setDefaultSoundProfile(@Nullable String id) { + public boolean setDefaultSoundProfile(@Nullable String soundProfileId) { try { - return mService.setDefaultSoundProfile(id, mUserHandle); + return mService.setDefaultSoundProfile(soundProfileId, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 7e655235732f..68c1983825a2 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -1269,9 +1269,6 @@ int APerformanceHint_notifyWorkloadIncrease(APerformanceHintSession* session, bo const char* debugName) { VALIDATE_PTR(session) VALIDATE_PTR(debugName) - if (!useNewLoadHintBehavior()) { - return ENOTSUP; - } return session->notifyWorkloadIncrease(cpu, gpu, debugName); } @@ -1279,9 +1276,6 @@ int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool const char* debugName) { VALIDATE_PTR(session) VALIDATE_PTR(debugName) - if (!useNewLoadHintBehavior()) { - return ENOTSUP; - } return session->notifyWorkloadReset(cpu, gpu, debugName); } @@ -1289,9 +1283,6 @@ int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool const char* debugName) { VALIDATE_PTR(session) VALIDATE_PTR(debugName) - if (!useNewLoadHintBehavior()) { - return ENOTSUP; - } return session->notifyWorkloadSpike(cpu, gpu, debugName); } diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index ef46906f9cf4..e4f07f9fc213 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -1364,8 +1364,6 @@ public class PackageWatchdog { Slog.w(TAG, "Failed to save monitored packages, restoring backup", e); mPolicyFile.failWrite(stream); return false; - } finally { - IoUtils.closeQuietly(stream); } } } diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java index 4a00ed3d1d46..5297c8b61aad 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -1372,8 +1372,6 @@ public class PackageWatchdog { Slog.w(TAG, "Failed to save monitored packages, restoring backup", e); mPolicyFile.failWrite(stream); return false; - } finally { - IoUtils.closeQuietly(stream); } } } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index 19fa5078fd5b..1ed814a2ae20 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -44,7 +44,7 @@ abstract class GetPreferenceGraphApiHandler( ): PreferenceGraphProto { val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request) if (request.screenKeys.isEmpty()) { - PreferenceScreenRegistry.preferenceScreenMetadataCreators.forEachKeyAsync { + PreferenceScreenRegistry.preferenceScreenMetadataFactories.forEachKeyAsync { builder.addPreferenceScreenFromRegistry(it) } for (provider in preferenceScreenProviders) { diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt index 1049cceb4f76..14e3b87cf325 100644 --- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt +++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt @@ -126,11 +126,11 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { it.write("package $outputPkg;\n\n") it.write("import $PACKAGE.FixedArrayMap;\n") it.write("import $PACKAGE.FixedArrayMap.OrderedInitializer;\n") - it.write("import $PACKAGE.$CREATOR;\n\n") + it.write("import $PACKAGE.$FACTORY;\n\n") it.write("// Generated by annotation processor for @$ANNOTATION_NAME\n") it.write("public final class $outputClass {\n") it.write(" private $outputClass() {}\n\n") - it.write(" public static FixedArrayMap<String, $CREATOR> $outputFun() {\n") + it.write(" public static FixedArrayMap<String, $FACTORY> $outputFun() {\n") val size = screens.size it.write(" return new FixedArrayMap<>($size, $outputClass::init);\n") it.write(" }\n\n") @@ -143,7 +143,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { if (overlay) it.write(" // overlay") it.write("\n") } - it.write(" private static void init(OrderedInitializer<String, $CREATOR> screens) {\n") + it.write(" private static void init(OrderedInitializer<String, $FACTORY> screens) {\n") var index = 0 while (index < size) { val screen = screens[index] @@ -225,7 +225,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { private const val ANNOTATION_NAME = "ProvidePreferenceScreen" private const val ANNOTATION = "$PACKAGE.$ANNOTATION_NAME" private const val PREFERENCE_SCREEN_METADATA = "PreferenceScreenMetadata" - private const val CREATOR = "PreferenceScreenMetadataCreator" + private const val FACTORY = "PreferenceScreenMetadataFactory" private const val OPTIONS_NAME = "ProvidePreferenceScreenOptions" private const val OPTIONS = "$PACKAGE.$OPTIONS_NAME" diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt index a2dcefb43436..850d4523e96e 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt @@ -65,8 +65,8 @@ interface PreferenceScreenMetadata : PreferenceMetadata { fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? = null } -/** Creator of [PreferenceScreenMetadata]. */ -fun interface PreferenceScreenMetadataCreator { +/** Factory of [PreferenceScreenMetadata]. */ +fun interface PreferenceScreenMetadataFactory { /** * Creates a new [PreferenceScreenMetadata]. diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index 6cf39f3ae8aa..9fc21343b6a0 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -26,11 +26,11 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { private lateinit var keyValueStoreProvider: KeyValueStoreProvider /** - * Creators of all available [PreferenceScreenMetadata]s. + * Factories of all available [PreferenceScreenMetadata]s. * * The map key is preference screen key. */ - var preferenceScreenMetadataCreators = FixedArrayMap<String, PreferenceScreenMetadataCreator>() + var preferenceScreenMetadataFactories = FixedArrayMap<String, PreferenceScreenMetadataFactory>() private var readWritePermitProvider: ReadWritePermitProvider = object : ReadWritePermitProvider {} @@ -51,7 +51,7 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { /** Creates [PreferenceScreenMetadata] of particular screen key. */ fun create(context: Context, screenKey: String?): PreferenceScreenMetadata? = - screenKey?.let { preferenceScreenMetadataCreators[it]?.create(context.applicationContext) } + screenKey?.let { preferenceScreenMetadataFactories[it]?.create(context.applicationContext) } /** * Sets the provider to check read write permit. Read and write requests are denied by default. diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt index 61b8b7f73754..41b1319a1fc8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt @@ -54,9 +54,11 @@ inline fun <T> Flow<List<T>>.filterItem(crossinline predicate: (T) -> Boolean): fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> = combine(otherFlow.take(1)) { value, _ -> value } - /** * Collects the latest value of given flow with a provided action with [LifecycleOwner]. + * + * This helper function is designed to work with non Compose code. For Compose, please collect the + * flow in a `LaunchedEffect` instead to ensure disposable and re-enter safe. */ fun <T> Flow<T>.collectLatestWithLifecycle( lifecycleOwner: LifecycleOwner, diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt index 0d73cb3e63c9..798e2d49ff57 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt @@ -160,23 +160,25 @@ class TogglePermissionAppInfoPageTest { @Test fun infoPage_whenChangeableAndClick() { val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = true) + val switchTitle = context.getString(listModel.switchTitleResId) setTogglePermissionAppInfoPage(listModel) - composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick() + composeTestRule.waitUntilExists(hasText(switchTitle)) + composeTestRule.onNodeWithText(switchTitle).performClick() - composeTestRule.waitUntilExists( - hasText(context.getString(listModel.switchTitleResId)) and isOn()) + composeTestRule.waitUntilExists(hasText(switchTitle) and isOn()) } @Test fun infoPage_whenNotChangeableAndClick() { val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false) + val switchTitle = context.getString(listModel.switchTitleResId) setTogglePermissionAppInfoPage(listModel) - composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick() + composeTestRule.waitUntilExists(hasText(switchTitle)) + composeTestRule.onNodeWithText(switchTitle).performClick() - composeTestRule.waitUntilExists( - hasText(context.getString(listModel.switchTitleResId)) and isOff()) + composeTestRule.waitUntilExists(hasText(switchTitle) and isOff()) } @Test diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index f1bbfc62681a..5b4ee8bdb339 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -110,6 +110,7 @@ public class SystemSettings { Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, Settings.System.MOUSE_SCROLLING_ACCELERATION, Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, + Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED, Settings.System.TOUCHPAD_POINTER_SPEED, Settings.System.TOUCHPAD_NATURAL_SCROLLING, Settings.System.TOUCHPAD_TAP_TO_CLICK, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 6abd9b73e26d..0432eeacec4d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -226,6 +226,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.MOUSE_REVERSE_VERTICAL_SCROLLING, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_SCROLLING_ACCELERATION, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.MOUSE_POINTER_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 31d5bfaccd5d..99d704f1eca8 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -86,6 +86,7 @@ <uses-permission android:name="android.permission.LOCATION_HARDWARE" /> <uses-permission android:name="android.permission.NETWORK_FACTORY" /> <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" /> + <uses-permission android:name="android.permission.INTERNET" /> <!-- Physical hardware --> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 8dd7c7aff150..7d201c18a141 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -753,13 +753,6 @@ flag { } flag { - name: "screenshot_context_url" - namespace: "systemui" - description: "Include optional app-provided context URL when sharing a screenshot." - bug: "242791070" -} - -flag { name: "run_fingerprint_detect_on_dismissible_keyguard" namespace: "systemui" description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible." diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt index 2233debde277..4ee6db3d516c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.compose.animation.scene.effect +package com.android.compose.gesture.effect import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec @@ -118,56 +118,3 @@ open class BaseContentOverscrollEffect( } } } - -/** An overscroll effect that ensures only a single fling animation is triggered. */ -internal class GestureEffect(private val delegate: ContentOverscrollEffect) : - ContentOverscrollEffect by delegate { - private var shouldFling = false - - override fun applyToScroll( - delta: Offset, - source: NestedScrollSource, - performScroll: (Offset) -> Offset, - ): Offset { - shouldFling = true - return delegate.applyToScroll(delta, source, performScroll) - } - - override suspend fun applyToFling( - velocity: Velocity, - performFling: suspend (Velocity) -> Velocity, - ) { - if (!shouldFling) { - performFling(velocity) - return - } - shouldFling = false - delegate.applyToFling(velocity, performFling) - } - - suspend fun ensureApplyToFlingIsCalled() { - applyToFling(Velocity.Zero) { Velocity.Zero } - } -} - -/** - * An overscroll effect that only applies visual effects and does not interfere with the actual - * scrolling or flinging behavior. - */ -internal class VisualEffect(private val delegate: ContentOverscrollEffect) : - ContentOverscrollEffect by delegate { - override fun applyToScroll( - delta: Offset, - source: NestedScrollSource, - performScroll: (Offset) -> Offset, - ): Offset { - return performScroll(delta) - } - - override suspend fun applyToFling( - velocity: Velocity, - performFling: suspend (Velocity) -> Velocity, - ) { - performFling(velocity) - } -} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt index f459c46d3e6f..d9924038d8ef 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.compose.animation.scene.effect +package com.android.compose.gesture.effect import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.AnimationSpec @@ -34,7 +34,6 @@ import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.ProgressConverter import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope @@ -80,7 +79,7 @@ class OffsetOverscrollEffect( ) @VisibleForTesting - internal fun computeOffset(density: Density, overscrollDistance: Float): Int { + fun computeOffset(density: Density, overscrollDistance: Float): Int { val maxDistancePx = with(density) { MaxDistance.toPx() } val progress = ProgressConverter.Default.convert(overscrollDistance / maxDistancePx) return (progress * maxDistancePx).roundToInt() @@ -98,3 +97,35 @@ fun rememberOffsetOverscrollEffect( OffsetOverscrollEffect(orientation, animationScope, animationSpec) } } + +/** This converter lets you change a linear progress into a function of your choice. */ +fun interface ProgressConverter { + fun convert(progress: Float): Float + + companion object { + /** Starts linearly with some resistance and slowly approaches to 0.2f */ + val Default = tanh(maxProgress = 0.2f, tilt = 3f) + + /** + * The scroll stays linear, with [factor] you can control how much resistance there is. + * + * @param factor If you choose a value between 0f and 1f, the progress will grow more + * slowly, like there's resistance. A value of 1f means there's no resistance. + */ + fun linear(factor: Float = 1f) = ProgressConverter { it * factor } + + /** + * This function starts linear and slowly approaches [maxProgress]. + * + * See a [visual representation](https://www.desmos.com/calculator/usgvvf0z1u) of this + * function. + * + * @param maxProgress is the maximum progress value. + * @param tilt behaves similarly to the factor in the [linear] function, and allows you to + * control how quickly you get to the [maxProgress]. + */ + fun tanh(maxProgress: Float, tilt: Float = 1f) = ProgressConverter { + maxProgress * kotlin.math.tanh(x = it / (maxProgress * tilt)) + } + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt index ca50e778d131..ca50e778d131 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt index da8fe3094448..5a3f240deb44 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.compose.animation.scene.effect +package com.android.compose.gesture.effect import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.rememberScrollableState diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index cfbe6671db02..ffdf509174d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -58,7 +58,7 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.animation.scene.effect.rememberOffsetOverscrollEffect +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 1480db9de701..5f991fbb50df 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -101,20 +101,21 @@ fun VolumeSlider( Column(modifier) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().height(40.dp), + verticalAlignment = Alignment.CenterVertically, ) { state.icon?.let { Icon( icon = it, tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(40.dp).padding(8.dp), + modifier = Modifier.size(24.dp), ) } Text( text = state.label, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.weight(1f).align(Alignment.CenterVertically), + modifier = Modifier.weight(1f), ) button?.invoke() } @@ -125,43 +126,47 @@ fun VolumeSlider( onValueChangeFinished = onValueChangeFinished, enabled = state.isEnabled, modifier = - Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics { - if (state.isEnabled) { - contentDescription = state.label - state.a11yClickDescription?.let { - customActions = - listOf( - CustomAccessibilityAction(it) { - onIconTapped() - true - } - ) - } - - state.a11yStateDescription?.let { stateDescription = it } - progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) - } else { - disabled() - contentDescription = - state.disabledMessage?.let { "${state.label}, $it" } ?: state.label - } - setProgress { targetValue -> - val targetDirection = - when { - targetValue > value -> 1 - targetValue < value -> -1 - else -> 0 + Modifier.height(40.dp) + .padding(vertical = 8.dp) + .sysuiResTag(state.label) + .clearAndSetSemantics { + if (state.isEnabled) { + contentDescription = state.label + state.a11yClickDescription?.let { + customActions = + listOf( + CustomAccessibilityAction(it) { + onIconTapped() + true + } + ) } - val newValue = - (value + targetDirection * state.a11yStep).coerceIn( - state.valueRange.start, - state.valueRange.endInclusive, - ) - onValueChange(newValue) - true - } - }, + state.a11yStateDescription?.let { stateDescription = it } + progressBarRangeInfo = + ProgressBarRangeInfo(state.value, state.valueRange) + } else { + disabled() + contentDescription = + state.disabledMessage?.let { "${state.label}, $it" } ?: state.label + } + setProgress { targetValue -> + val targetDirection = + when { + targetValue > value -> 1 + targetValue < value -> -1 + else -> 0 + } + + val newValue = + (value + targetDirection * state.a11yStep).coerceIn( + state.valueRange.start, + state.valueRange.endInclusive, + ) + onValueChange(newValue) + true + } + }, ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 7b30a2a475e3..c704a3e96467 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection -import com.android.compose.animation.scene.effect.ContentOverscrollEffect +import com.android.compose.gesture.effect.ContentOverscrollEffect /** * [SceneTransitionLayout] is a container that automatically animates its content whenever its state diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 8794df0cf884..fda6fab6229a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.Transformation -import kotlin.math.tanh /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -476,35 +475,3 @@ interface PropertyTransformationBuilder { /** Apply a [transformation] to the element(s) matching [matcher]. */ fun transformation(matcher: ElementMatcher, transformation: Transformation.Factory) } - -/** This converter lets you change a linear progress into a function of your choice. */ -fun interface ProgressConverter { - fun convert(progress: Float): Float - - companion object { - /** Starts linearly with some resistance and slowly approaches to 0.2f */ - val Default = tanh(maxProgress = 0.2f, tilt = 3f) - - /** - * The scroll stays linear, with [factor] you can control how much resistance there is. - * - * @param factor If you choose a value between 0f and 1f, the progress will grow more - * slowly, like there's resistance. A value of 1f means there's no resistance. - */ - fun linear(factor: Float = 1f) = ProgressConverter { it * factor } - - /** - * This function starts linear and slowly approaches [maxProgress]. - * - * See a [visual representation](https://www.desmos.com/calculator/usgvvf0z1u) of this - * function. - * - * @param maxProgress is the maximum progress value. - * @param tilt behaves similarly to the factor in the [linear] function, and allows you to - * control how quickly you get to the [maxProgress]. - */ - fun tanh(maxProgress: Float, tilt: Float = 1f) = ProgressConverter { - maxProgress * tanh(x = it / (maxProgress * tilt)) - } - } -} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 8c5a72738a41..4c15f7a4534f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -53,10 +53,10 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState import com.android.compose.animation.scene.effect.GestureEffect -import com.android.compose.animation.scene.effect.OffsetOverscrollEffect import com.android.compose.animation.scene.effect.VisualEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions +import com.android.compose.gesture.effect.OffsetOverscrollEffect import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.ContainerState import com.android.compose.ui.graphics.container diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt new file mode 100644 index 000000000000..2db45aa3dd58 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt @@ -0,0 +1,75 @@ +/* + * 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.compose.animation.scene.effect + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.unit.Velocity +import com.android.compose.gesture.effect.ContentOverscrollEffect + +/** An overscroll effect that ensures only a single fling animation is triggered. */ +internal class GestureEffect(private val delegate: ContentOverscrollEffect) : + ContentOverscrollEffect by delegate { + private var shouldFling = false + + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset { + shouldFling = true + return delegate.applyToScroll(delta, source, performScroll) + } + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + if (!shouldFling) { + performFling(velocity) + return + } + shouldFling = false + delegate.applyToFling(velocity, performFling) + } + + suspend fun ensureApplyToFlingIsCalled() { + applyToFling(Velocity.Zero) { Velocity.Zero } + } +} + +/** + * An overscroll effect that only applies visual effects and does not interfere with the actual + * scrolling or flinging behavior. + */ +internal class VisualEffect(private val delegate: ContentOverscrollEffect) : + ContentOverscrollEffect by delegate { + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset { + return performScroll(delta) + } + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + performFling(velocity) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 676903274b38..53495be7b02a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -70,8 +70,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC -import com.android.compose.animation.scene.effect.OffsetOverscrollEffect import com.android.compose.animation.scene.subjects.assertThat +import com.android.compose.gesture.effect.OffsetOverscrollEffect import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.setContentAndCreateMainScope import com.android.compose.test.transition diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java index b08f97a646b9..e00f6a6d6418 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java @@ -16,11 +16,16 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.graphics.drawable.Drawable; +import android.platform.test.annotations.EnableFlags; +import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; @@ -28,15 +33,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder; import com.android.systemui.res.R; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -44,32 +52,35 @@ import java.util.List; /** Tests for {@link AccessibilityTargetAdapter}. */ @SmallTest @RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class AccessibilityTargetAdapterTest extends SysuiTestCase { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final ComponentName TEST_NAME = new ComponentName("test.pkg", "test.activitty"); + private static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1; + @Mock private AccessibilityTarget mAccessibilityTarget; - @Mock private Drawable mIcon; - @Mock private Drawable.ConstantState mConstantState; - private ViewHolder mViewHolder; private AccessibilityTargetAdapter mAdapter; private final List<AccessibilityTarget> mTargets = new ArrayList<>(); @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - mTargets.add(mAccessibilityTarget); - mAdapter = new AccessibilityTargetAdapter(mTargets); - final View rootView = LayoutInflater.from(mContext).inflate( R.layout.accessibility_floating_menu_item, null); mViewHolder = new ViewHolder(rootView); when(mAccessibilityTarget.getIcon()).thenReturn(mIcon); + when(mAccessibilityTarget.getId()).thenReturn(TEST_NAME.flattenToString()); when(mIcon.getConstantState()).thenReturn(mConstantState); + + mTargets.add(mAccessibilityTarget); + mAdapter = new AccessibilityTargetAdapter(mTargets); } @Test @@ -105,4 +116,77 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase { assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals( "testState")).isTrue(); } + + @Test + @EnableFlags( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void onHearingDeviceStatusChanged_disconnected_getExpectedStateDescription() { + when(mAccessibilityTarget.getId()).thenReturn( + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + int indexInTarget = 0; + + mAdapter.onHearingDeviceStatusChanged(ConnectionStatus.DISCONNECTED, indexInTarget); + mAdapter.onBindViewHolder(mViewHolder, indexInTarget); + + assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals( + "Disconnected")).isTrue(); + } + + @Test + @EnableFlags( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void onBindViewHolder_withPayloadDisconnected_getExpectedStateDescription() { + when(mAccessibilityTarget.getId()).thenReturn( + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + int indexInTarget = 0; + + mAdapter.onHearingDeviceStatusChanged(ConnectionStatus.DISCONNECTED, indexInTarget); + mAdapter.onBindViewHolder(mViewHolder, indexInTarget, + List.of(PAYLOAD_HEARING_STATUS_DRAWABLE)); + + assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals( + "Disconnected")).isTrue(); + } + + @Test + @EnableFlags( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void setBadgeOnLeftSide_false_rightBadgeVisibleAndLeftBadgeInvisible() { + when(mAccessibilityTarget.getId()).thenReturn( + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + + mAdapter.setBadgeOnLeftSide(false); + mAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test + @EnableFlags( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void setBadgeOnLeftSide_rightBadgeInvisibleAndLeftBadgeVisible() { + when(mAccessibilityTarget.getId()).thenReturn( + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + + mAdapter.setBadgeOnLeftSide(true); + mAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + @EnableFlags( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void setBadgeOnLeftSide_bindViewHolderPayloads_rightBadgeInvisibleAndLeftBadgeVisible() { + when(mAccessibilityTarget.getId()).thenReturn( + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); + + mAdapter.setBadgeOnLeftSide(true); + mAdapter.onBindViewHolder(mViewHolder, 0, List.of(PAYLOAD_HEARING_STATUS_DRAWABLE)); + + assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.VISIBLE); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index fbd8a713a249..715c40a31632 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -113,7 +113,8 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController, mDragToInteractAnimationController); - final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets); + final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter( + mStubTargets); mStubListView = (RecyclerView) mStubMenuView.getChildAt(0); mStubListView.setAdapter(stubAdapter); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt index f68a1b5a17e9..eae5728f586d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.display.data.repository -import android.content.testableContext +import android.content.Context import android.platform.test.annotations.EnableFlags import android.view.Display import android.view.layoutInflater @@ -24,6 +24,7 @@ import android.view.mockWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.SysuiTestableContext import com.android.systemui.display.shared.model.DisplayWindowProperties import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope @@ -36,8 +37,12 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.doAnswer +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) @RunWith(AndroidJUnit4::class) @@ -48,7 +53,8 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { private val fakeDisplayRepository = kosmos.displayRepository private val testScope = kosmos.testScope - private val applicationContext = kosmos.testableContext + private val applicationContext = spy(context) + private val applicationWindowManager = kosmos.mockWindowManager private val applicationLayoutInflater = kosmos.layoutInflater @@ -64,6 +70,22 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { } @Before + fun setUpContext() { + doAnswer { createContextForDisplay(it.arguments[0] as Display) } + .whenever(applicationContext) + .createWindowContext(any(), any(), any()) + } + + private fun createContextForDisplay(display: Display): Context { + if (display.displayId == BEING_REMOVED_DISPLAY_ID) { + // Simulate what happens when a display is being removed. + // Return a context with the same display id as the original context. + return mContext + } + return SysuiTestableContext(mContext).also { it.display = display } + } + + @Before fun start() { repo.start() } @@ -72,6 +94,7 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID)) fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) + fakeDisplayRepository.addDisplay(createDisplay(BEING_REMOVED_DISPLAY_ID)) } @Test @@ -94,7 +117,7 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { @Test fun get_nonDefaultDisplayId_returnsNewStatusBarContext() = testScope.runTest { - val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)!! assertThat(displayContext.context).isNotSameInstanceAs(applicationContext) } @@ -102,7 +125,7 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { @Test fun get_nonDefaultDisplayId_returnsNewWindowManager() = testScope.runTest { - val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)!! assertThat(displayContext.windowManager).isNotSameInstanceAs(applicationWindowManager) } @@ -110,7 +133,7 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { @Test fun get_nonDefaultDisplayId_returnsNewLayoutInflater() = testScope.runTest { - val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO) + val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)!! assertThat(displayContext.layoutInflater).isNotSameInstanceAs(applicationLayoutInflater) } @@ -154,17 +177,26 @@ class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() { .isNotSameInstanceAs(displayContext) } - @Test(expected = IllegalArgumentException::class) - fun get_nonExistingDisplayId_throws() = - testScope.runTest { repo.get(NON_EXISTING_DISPLAY_ID, WINDOW_TYPE_FOO) } + @Test + fun get_nonExistingDisplayId_returnsNull() = + testScope.runTest { + assertThat(repo.get(NON_EXISTING_DISPLAY_ID, WINDOW_TYPE_FOO)).isNull() + } + + @Test + fun get_displayBeingRemoved_returnsNull() = + testScope.runTest { + assertThat(repo.get(BEING_REMOVED_DISPLAY_ID, WINDOW_TYPE_FOO)).isNull() + } private fun createDisplay(displayId: Int) = - mock<Display> { on { getDisplayId() } doReturn displayId } + mock<Display> { on { getDisplayId() } doReturn (displayId) } companion object { private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1 private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2 + private const val BEING_REMOVED_DISPLAY_ID = DEFAULT_DISPLAY_ID + 4 private const val WINDOW_TYPE_FOO = 123 private const val WINDOW_TYPE_BAR = 321 } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt index 6a0781b3580f..73957eb488d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt @@ -80,9 +80,9 @@ class PerDisplayStoreImplTest : SysuiTestCase() { assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(instance) } - @Test(expected = IllegalArgumentException::class) - fun forDisplay_nonExistingDisplayId_throws() = - testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } + @Test + fun forDisplay_nonExistingDisplayId_returnsNull() = + testScope.runTest { assertThat(store.forDisplay(NON_EXISTING_DISPLAY_ID)).isNull() } @Test fun forDisplay_afterDisplayRemoved_onDisplayRemovalActionInvoked() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index ba6518f9bd2b..cd30eddb1901 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -24,9 +24,9 @@ import android.os.UserHandle import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.logging.UiEventLogger -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.screenshot.ui.viewmodel.PreviewAction +import com.android.systemui.shared.Flags import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlin.test.Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java index 33a08035a7b2..1938f429264b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java @@ -264,7 +264,9 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { @After public void tearDown() throws Exception { - mTextView.setAnimationsEnabled(true); + if (mTextView != null) { + mTextView.setAnimationsEnabled(true); + } if (mController != null) { mController.destroy(); mController = null; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt index 009b33b9f808..3515c5649ca0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt @@ -26,10 +26,12 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.plugins.fakeDarkIconDispatcher import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory +import com.android.systemui.statusbar.policy.statusBarConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.testKosmos @@ -77,6 +79,8 @@ class StatusBarInitializerTest : SysuiTestCase() { componentFactory = mock(HomeStatusBarComponent.Factory::class.java), creationListeners = setOf(), statusBarModePerDisplayRepository = statusBarModePerDisplayRepository, + darkIconDispatcher = kosmos.fakeDarkIconDispatcher, + statusBarConfigurationController = kosmos.statusBarConfigurationController, ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt index 18eef33813f6..884c35c3457d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt @@ -51,7 +51,7 @@ class LightBarControllerStoreImplTest : SysuiTestCase() { @Test fun forDisplay_startsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance).start() } @@ -59,7 +59,7 @@ class LightBarControllerStoreImplTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -67,7 +67,7 @@ class LightBarControllerStoreImplTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt index a2c3c66f4448..f37648a639df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt @@ -56,7 +56,7 @@ class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -64,7 +64,7 @@ class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt index 4a26fdf20e6e..e0a1f273aa44 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt @@ -51,7 +51,7 @@ class MultiDisplayStatusBarContentInsetsProviderStoreTest : SysuiTestCase() { @Test fun forDisplay_startsInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance).start() } @@ -59,7 +59,7 @@ class MultiDisplayStatusBarContentInsetsProviderStoreTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -67,7 +67,7 @@ class MultiDisplayStatusBarContentInsetsProviderStoreTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt index a9920ec5e29b..11fd902fc50c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt @@ -53,7 +53,7 @@ class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() { @Test fun forDisplay_startsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance).start() } @@ -61,7 +61,7 @@ class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt index e65c04c45382..3cc592c94678 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt @@ -56,7 +56,7 @@ class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -64,7 +64,7 @@ class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index be20bc1bf9d4..d86c6efce284 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -225,7 +225,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 123 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(emptyList(), 0f, 0xAABBCC) - val iconColors by collectLastValue(underTest.iconColors(displayId)) + val iconColors by collectLastValue(underTest.iconColors(displayId)!!) assertThat(iconColors).isNotNull() assertThat(iconColors!!.tint).isEqualTo(0xAABBCC) @@ -241,7 +241,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 321 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) - val iconColors by collectLastValue(underTest.iconColors(displayId)) + val iconColors by collectLastValue(underTest.iconColors(displayId)!!) val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7)) assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) } @@ -252,7 +252,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val displayId = 987 darkIconRepository.darkState(displayId).value = SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) - val iconColors by collectLastValue(underTest.iconColors(displayId)) + val iconColors by collectLastValue(underTest.iconColors(displayId)!!) assertThat(iconColors!!.staticDrawableColor(Rect(6, 6, 7, 7))) .isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 14bbd38ece2c..72a91bc12f8d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -279,7 +279,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { notification); RemoteViews headerRemoteViews; if (lowPriority) { - headerRemoteViews = builder.makeLowPriorityContentView(true); + headerRemoteViews = builder.makeLowPriorityContentView(true, false); } else { headerRemoteViews = builder.makeNotificationGroupHeader(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt index 90506a1b9a7f..d16372611e88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt @@ -56,7 +56,7 @@ class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -64,7 +64,7 @@ class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt index 2d9880a48f80..659d91a95aa8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt @@ -39,7 +39,7 @@ class LightsOutInteractorTest : SysuiTestCase() { fun isLowProfile_lightsOutStatusBarMode_false() = runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.LIGHTS_OUT - val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)!!) assertThat(actual).isTrue() } @@ -49,7 +49,7 @@ class LightsOutInteractorTest : SysuiTestCase() { statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.LIGHTS_OUT_TRANSPARENT - val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)!!) assertThat(actual).isTrue() } @@ -58,7 +58,7 @@ class LightsOutInteractorTest : SysuiTestCase() { fun isLowProfile_transparentStatusBarMode_false() = runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT - val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)) + val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID)!!) assertThat(actual).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt index 7a9d0179b239..769f012bfdf7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt @@ -53,7 +53,7 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { @Test fun beforeDisplayRemoved_doesNotStopInstances() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! verify(instance, never()).stop() } @@ -61,7 +61,7 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { @Test fun displayRemoved_stopsInstance() = testScope.runTest { - val instance = underTest.forDisplay(DEFAULT_DISPLAY) + val instance = underTest.forDisplay(DEFAULT_DISPLAY)!! fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt index 8972f3ec71af..8b526bb1d55b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt @@ -30,12 +30,14 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecogn import com.android.systemui.touchpad.ui.gesture.FakeVelocityTracker import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest +@Ignore("b/386412866") @RunWith(ParameterizedAndroidJunit4::class) class ThreeFingerGestureRecognizerTest( private val recognizer: GestureRecognizer, diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml index 2067f85249a2..b1e4272c4004 100644 --- a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml +++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml @@ -15,18 +15,60 @@ limitations under the License. --> -<LinearLayout +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="@dimen/accessibility_floating_menu_small_padding" android:paddingEnd="@dimen/accessibility_floating_menu_small_padding" - android:orientation="vertical" - android:gravity="center"> + android:orientation="vertical"> <View android:id="@+id/icon_view" android:layout_width="@dimen/accessibility_floating_menu_small_width_height" - android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/> + android:layout_height="@dimen/accessibility_floating_menu_small_width_height" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"/> -</LinearLayout>
\ No newline at end of file + <androidx.constraintlayout.widget.Guideline + android:id="@+id/app_icon_constraint_right_badge_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_badge_position" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/app_icon_constraint_badge_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_badge_position" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/app_icon_constraint_left_badge_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_left_badge_x_position" /> + + <View + android:id="@+id/right_badge_view" + android:layout_width="@dimen/accessibility_floating_menu_small_badge_width_height" + android:layout_height="@dimen/accessibility_floating_menu_small_badge_width_height" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_right_badge_vertical" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@id/app_icon_constraint_badge_horizontal" + android:visibility="invisible" /> + + <View + android:id="@+id/left_badge_view" + android:layout_width="@dimen/accessibility_floating_menu_small_badge_width_height" + android:layout_height="@dimen/accessibility_floating_menu_small_badge_width_height" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_left_badge_vertical" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@id/app_icon_constraint_badge_horizontal" + android:visibility="invisible" /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 11327b648ddc..570197edf876 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1683,10 +1683,14 @@ <dimen name="accessibility_floating_menu_margin">16dp</dimen> <dimen name="accessibility_floating_menu_small_padding">6dp</dimen> <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen> + <dimen name="accessibility_floating_menu_small_badge_width_height">12dp</dimen> + <dimen name="accessibility_floating_menu_badge_position">0.67</dimen> + <dimen name="accessibility_floating_menu_left_badge_x_position">-0.67</dimen> <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen> <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen> <dimen name="accessibility_floating_menu_large_padding">8dp</dimen> <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen> + <dimen name="accessibility_floating_menu_large_badge_width_height">18dp</dimen> <dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen> <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen> <dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java index 5160309e7c72..697d16b3336c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -16,12 +16,17 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; + +import android.content.ComponentName; +import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.RecyclerView; @@ -29,7 +34,10 @@ import androidx.recyclerview.widget.RecyclerView.Adapter; import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType; import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.settingslib.bluetooth.HearingAidDeviceManager; +import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus; import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder; +import com.android.systemui.accessibility.hearingaid.HearingDeviceStatusDrawableInfo; import com.android.systemui.res.R; import java.lang.annotation.Retention; @@ -40,10 +48,17 @@ import java.util.List; * An adapter which shows the set of accessibility targets that can be performed. */ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { + @VisibleForTesting + static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1; + private int mIconWidthHeight; + private int mBadgeWidthHeight; private int mItemPadding; private final List<AccessibilityTarget> mTargets; + private int mHearingDeviceStatus; + private boolean mBadgeOnLeftSide = false; + @IntDef({ ItemType.FIRST_ITEM, ItemType.REGULAR_ITEM, @@ -56,7 +71,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { int LAST_ITEM = 2; } - public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) { + public AccessibilityTargetAdapter(@NonNull List<AccessibilityTarget> targets) { mTargets = targets; } @@ -82,7 +97,9 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { public void onBindViewHolder(@NonNull ViewHolder holder, int position) { final AccessibilityTarget target = mTargets.get(position); holder.mIconView.setBackground(target.getIcon()); - holder.updateIconWidthHeight(mIconWidthHeight); + holder.mRightBadgeView.setBackground(null); + holder.mLeftBadgeView.setBackground(null); + holder.updateIconSize(mIconWidthHeight); holder.updateItemPadding(mItemPadding, getItemCount()); holder.itemView.setOnClickListener((v) -> target.onSelected()); holder.itemView.setStateDescription(target.getStateDescription()); @@ -95,6 +112,32 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { ViewCompat.replaceAccessibilityAction(holder.itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, clickHint, /* command= */ null); + + if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) { + if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals( + ComponentName.unflattenFromString(target.getId()))) { + updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus); + } + } + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position, + @NonNull List<Object> payloads) { + if (payloads.isEmpty()) { + onBindViewHolder(holder, position); + return; + } + + if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) { + payloads.forEach(payload -> { + if (payload instanceof Integer cmd) { + if (cmd == PAYLOAD_HEARING_STATUS_DRAWABLE) { + updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus); + } + } + }); + } } @ItemType @@ -122,19 +165,76 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { mIconWidthHeight = iconWidthHeight; } + public void setBadgeWidthHeight(int badgeWidthHeight) { + mBadgeWidthHeight = badgeWidthHeight; + } + public void setItemPadding(int itemPadding) { mItemPadding = itemPadding; } + public void setBadgeOnLeftSide(boolean leftSide) { + mBadgeOnLeftSide = leftSide; + } + + /** + * Notifies to update the hearing device status drawable at the given target index. + * + * @param status the connection status for hearing devices. + * {@link HearingAidDeviceManager.ConnectionStatus} + * @param targetIndex The index of the hearing aid device in the target list, or -1 if not + * exist. + */ + public void onHearingDeviceStatusChanged(@HearingAidDeviceManager.ConnectionStatus int status, + int targetIndex) { + mHearingDeviceStatus = status; + + if (targetIndex >= 0) { + notifyItemChanged(targetIndex, PAYLOAD_HEARING_STATUS_DRAWABLE); + } + } + + private void updateHearingDeviceStatusDrawable(ViewHolder holder, + @ConnectionStatus int status) { + final Context context = holder.itemView.getContext(); + HearingDeviceStatusDrawableInfo.StatusDrawableInfo statusDrawableInfo = + HearingDeviceStatusDrawableInfo.get(status); + final int baseDrawableId = statusDrawableInfo.baseDrawableId(); + final int stateDescriptionId = statusDrawableInfo.stateDescriptionId(); + final int indicatorDrawableId = statusDrawableInfo.indicatorDrawableId(); + + holder.mIconView.setBackground( + (baseDrawableId != 0) ? context.getDrawable(baseDrawableId) : null); + holder.mRightBadgeView.setBackground( + (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null); + holder.mLeftBadgeView.setBackground( + (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null); + holder.itemView.setStateDescription( + (stateDescriptionId != 0) ? context.getString(stateDescriptionId) : null); + holder.updateBadgeSize(mBadgeWidthHeight); + + if (mBadgeOnLeftSide) { + holder.mRightBadgeView.setVisibility(View.INVISIBLE); + holder.mLeftBadgeView.setVisibility(View.VISIBLE); + } else { + holder.mRightBadgeView.setVisibility(View.VISIBLE); + holder.mLeftBadgeView.setVisibility(View.INVISIBLE); + } + } + static class ViewHolder extends RecyclerView.ViewHolder { final View mIconView; + final View mRightBadgeView; + final View mLeftBadgeView; ViewHolder(View itemView) { super(itemView); mIconView = itemView.findViewById(R.id.icon_view); + mRightBadgeView = itemView.findViewById(R.id.right_badge_view); + mLeftBadgeView = itemView.findViewById(R.id.left_badge_view); } - void updateIconWidthHeight(int newValue) { + void updateIconSize(int newValue) { final ViewGroup.LayoutParams layoutParams = mIconView.getLayoutParams(); if (layoutParams.width == newValue) { return; @@ -144,6 +244,24 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { mIconView.setLayoutParams(layoutParams); } + void updateBadgeSize(int newValue) { + final ViewGroup.LayoutParams rightLayoutParams = mRightBadgeView.getLayoutParams(); + if (rightLayoutParams.width == newValue) { + return; + } + rightLayoutParams.width = newValue; + rightLayoutParams.height = newValue; + final ViewGroup.LayoutParams leftLayoutParams = mLeftBadgeView.getLayoutParams(); + if (leftLayoutParams.width == newValue) { + return; + } + leftLayoutParams.width = newValue; + leftLayoutParams.height = newValue; + + mRightBadgeView.setLayoutParams(rightLayoutParams); + mLeftBadgeView.setLayoutParams(leftLayoutParams); + } + void updateItemPadding(int padding, int size) { itemView.setPaddingRelative(padding, padding, padding, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 23fc546dd9b3..3f49010aaaab 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -90,6 +90,7 @@ class MenuView extends FrameLayout implements mTargetFeaturesView = new RecyclerView(context); mTargetFeaturesView.setAdapter(mAdapter); mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); + mTargetFeaturesView.setClipChildren(false); setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); // Avoid drawing out of bounds of the parent view setClipToOutline(true); @@ -157,6 +158,14 @@ class MenuView extends FrameLayout implements private void onItemSizeChanged() { mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding()); mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize()); + mAdapter.setBadgeWidthHeight(mMenuViewAppearance.getBadgeIconSize()); + mAdapter.notifyDataSetChanged(); + } + + @SuppressLint("NotifyDataSetChanged") + void onSideChanged() { + // Badge should be on different side of Menu view's side. + mAdapter.setBadgeOnLeftSide(!mMenuViewAppearance.isMenuOnLeftSide()); mAdapter.notifyDataSetChanged(); } @@ -205,6 +214,7 @@ class MenuView extends FrameLayout implements mMenuViewAppearance.setPercentagePosition(percentagePosition); onPositionChanged(); + onSideChanged(); } void onPositionChanged() { @@ -246,6 +256,8 @@ class MenuView extends FrameLayout implements mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding()); mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize()); + mAdapter.setBadgeWidthHeight(mMenuViewAppearance.getBadgeIconSize()); + mAdapter.notifyDataSetChanged(); onSizeChanged(); @@ -313,6 +325,7 @@ class MenuView extends FrameLayout implements mMenuViewAppearance.setPercentagePosition(percentagePosition); onEdgeChangedIfNeeded(); + onSideChanged(); } boolean isMoveToTucked() { @@ -434,11 +447,21 @@ class MenuView extends FrameLayout implements } private void updateHearingDeviceStatus(@HearingAidDeviceManager.ConnectionStatus int status) { - // TODO: b/357882387 - To update status drawable according to the status here. + final int haStatus = mMenuViewModel.getHearingDeviceStatusData().getValue(); + final int haPosition = mMenuViewModel.getHearingDeviceTargetIndexData().getValue(); + if (haPosition >= 0) { + mContext.getMainExecutor().execute( + () -> mAdapter.onHearingDeviceStatusChanged(haStatus, haPosition)); + } } private void updateHearingDeviceTargetIndex(int position) { - // TODO: b/357882387 - To update status drawable according to the status here. + final int haStatus = mMenuViewModel.getHearingDeviceStatusData().getValue(); + final int haPosition = mMenuViewModel.getHearingDeviceTargetIndexData().getValue(); + if (haPosition >= 0) { + mContext.getMainExecutor().execute( + () -> mAdapter.onHearingDeviceStatusChanged(haStatus, haPosition)); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index 760e1c374e31..a700cbef2e16 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -57,6 +57,8 @@ class MenuViewAppearance { private int mLargePadding; private int mSmallIconSize; private int mLargeIconSize; + private int mSmallBadgeSize; + private int mLargeBadgeSize; private int mSmallSingleRadius; private int mSmallMultipleRadius; private int mLargeSingleRadius; @@ -97,6 +99,12 @@ class MenuViewAppearance { mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height); mLargeIconSize = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_width_height); + mSmallBadgeSize = + mRes.getDimensionPixelSize( + R.dimen.accessibility_floating_menu_small_badge_width_height); + mLargeBadgeSize = + mRes.getDimensionPixelSize( + R.dimen.accessibility_floating_menu_large_badge_width_height); mSmallSingleRadius = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius); mSmallMultipleRadius = mRes.getDimensionPixelSize( @@ -211,6 +219,10 @@ class MenuViewAppearance { return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize; } + int getBadgeIconSize() { + return mSizeType == SMALL ? mSmallBadgeSize : mLargeBadgeSize; + } + private int getMenuMargin() { return mMargin; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java new file mode 100644 index 000000000000..ff9bfec7f5bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid; + +import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus; + +/** + * A utility class to get the hearing device status drawable and its description for the + * given connection status. Hearing device status drawable combine with base and indicator + * drawable. + */ +public final class HearingDeviceStatusDrawableInfo { + + private static final StatusDrawableInfo DRAWABLE_DEFAULT_INFO = new StatusDrawableInfo( + com.android.internal.R.drawable.ic_accessibility_hearing_aid, + 0, + 0); + private static final StatusDrawableInfo DRAWABLE_DISCONNECTED_INFO = new StatusDrawableInfo( + com.android.internal.R.drawable.ic_accessibility_hearing_aid_disconnected, + 0, + com.android.internal.R.string.hearing_device_status_disconnected); + private static final StatusDrawableInfo DRAWABLE_CONNECTED_INFO = new StatusDrawableInfo( + com.android.internal.R.drawable.ic_accessibility_hearing_aid, + com.android.internal.R.drawable.ic_accessibility_hearing_aid_blue_dot, + com.android.internal.R.string.hearing_device_status_connected); + private static final StatusDrawableInfo DRAWABLE_ACTIVE_INFO = new StatusDrawableInfo( + com.android.internal.R.drawable.ic_accessibility_hearing_aid, + com.android.internal.R.drawable.ic_accessibility_hearing_aid_green_dot, + com.android.internal.R.string.hearing_device_status_active); + + private HearingDeviceStatusDrawableInfo() {} + + /** + * Returns the corresponding {@link StatusDrawableInfo} for the given {@link ConnectionStatus}. + */ + public static StatusDrawableInfo get(@ConnectionStatus int status) { + return switch (status) { + case ConnectionStatus.DISCONNECTED -> DRAWABLE_DISCONNECTED_INFO; + case ConnectionStatus.CONNECTED -> DRAWABLE_CONNECTED_INFO; + case ConnectionStatus.ACTIVE -> DRAWABLE_ACTIVE_INFO; + // TODO: b/357882387 - Handle to show connecting or disconnecting status drawable + case ConnectionStatus.CONNECTING_OR_DISCONNECTING, ConnectionStatus.NO_DEVICE_BONDED -> + DRAWABLE_DEFAULT_INFO; + default -> DRAWABLE_DEFAULT_INFO; + }; + } + + /** + * A data class that holds the base drawable, indicator drawable and state description to + * represent hearing device connection status. + * + * @param baseDrawableId the base drawable id for the hearing device status + * @param indicatorDrawableId the indicator drawable id for the hearing device status + * @param stateDescriptionId the description for the hearing device status + */ + public record StatusDrawableInfo(int baseDrawableId, int indicatorDrawableId, + int stateDescriptionId) { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt index f310b30c1a81..3390640fa6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt @@ -18,6 +18,8 @@ package com.android.systemui.display.data.repository import android.annotation.SuppressLint import android.content.Context +import android.os.Bundle +import android.util.Log import android.view.Display import android.view.LayoutInflater import android.view.WindowManager @@ -39,14 +41,13 @@ import kotlinx.coroutines.CoroutineScope interface DisplayWindowPropertiesRepository { /** - * Returns a [DisplayWindowProperties] instance for a given display id and window type. - * - * @throws IllegalArgumentException if no display with the given display id exists. + * Returns a [DisplayWindowProperties] instance for a given display id and window type, or null + * if no display with the given display id exists. */ fun get( displayId: Int, @WindowManager.LayoutParams.WindowType windowType: Int, - ): DisplayWindowProperties + ): DisplayWindowProperties? } @SysUISingleton @@ -72,12 +73,10 @@ constructor( override fun get( displayId: Int, @WindowManager.LayoutParams.WindowType windowType: Int, - ): DisplayWindowProperties { - val display = - displayRepository.getDisplay(displayId) - ?: throw IllegalArgumentException("Display with id $displayId doesn't exist") + ): DisplayWindowProperties? { + val display = displayRepository.getDisplay(displayId) ?: return null return properties.get(displayId, windowType) - ?: create(display, windowType).also { properties.put(displayId, windowType, it) } + ?: create(display, windowType)?.also { properties.put(displayId, windowType, it) } } override fun start() { @@ -88,7 +87,7 @@ constructor( } } - private fun create(display: Display, windowType: Int): DisplayWindowProperties { + private fun create(display: Display, windowType: Int): DisplayWindowProperties? { val displayId = display.displayId return if (displayId == Display.DEFAULT_DISPLAY) { // For the default display, we can just reuse the global/application properties. @@ -102,6 +101,14 @@ constructor( ) } else { val context = createWindowContext(display, windowType) + if (context.displayId != display.displayId) { + Log.e( + TAG, + "Returning null because the new context doesn't have the desired display id " + + "${display.displayId}. Display was already removed.", + ) + return null + } @SuppressLint("NonInjectedService") // Need to manually get the service val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager val layoutInflater = LayoutInflater.from(context) @@ -110,11 +117,15 @@ constructor( } private fun createWindowContext(display: Display, windowType: Int): Context = - globalContext.createWindowContext(display, windowType, /* options= */ null).also { + globalContext.createWindowContext(display, windowType, /* options= */ Bundle.EMPTY).also { it.setTheme(R.style.Theme_SystemUI) } override fun dump(pw: PrintWriter, args: Array<out String>) { pw.write("perDisplayContexts: $properties") } + + private companion object { + const val TAG = "DisplayWindowPropsRepo" + } } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt index 711534f9dbf0..564588c159bd 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt @@ -16,6 +16,7 @@ package com.android.systemui.display.data.repository +import android.util.Log import android.view.Display import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable @@ -36,12 +37,10 @@ interface PerDisplayStore<T> { val defaultDisplay: T /** - * Returns an instance for a specific display id. - * - * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing - * displays. + * Returns an instance for a specific display id, or null if [displayId] doesn't match the id of + * any existing displays. */ - fun forDisplay(displayId: Int): T + fun forDisplay(displayId: Int): T? } abstract class PerDisplayStoreImpl<T>( @@ -58,7 +57,7 @@ abstract class PerDisplayStoreImpl<T>( * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. */ override val defaultDisplay: T - get() = forDisplay(Display.DEFAULT_DISPLAY) + get() = forDisplay(Display.DEFAULT_DISPLAY)!! /** * Returns an instance for a specific display id. @@ -66,16 +65,30 @@ abstract class PerDisplayStoreImpl<T>( * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing * displays. */ - override fun forDisplay(displayId: Int): T { + override fun forDisplay(displayId: Int): T? { if (displayRepository.getDisplay(displayId) == null) { - throw IllegalArgumentException("Display with id $displayId doesn't exist.") + Log.e(TAG, "<${instanceClass.simpleName}>: Display with id $displayId doesn't exist.") + return null } - return perDisplayInstances.computeIfAbsent(displayId) { - createInstanceForDisplay(displayId) + synchronized(perDisplayInstances) { + val existingInstance = perDisplayInstances[displayId] + if (existingInstance != null) { + return existingInstance + } + val newInstance = createInstanceForDisplay(displayId) + if (newInstance == null) { + Log.e( + TAG, + "<${instanceClass.simpleName}> returning null because createInstanceForDisplay($displayId) returned null.", + ) + } else { + perDisplayInstances[displayId] = newInstance + } + return newInstance } } - protected abstract fun createInstanceForDisplay(displayId: Int): T + protected abstract fun createInstanceForDisplay(displayId: Int): T? override fun start() { val instanceType = instanceClass.simpleName @@ -98,6 +111,10 @@ abstract class PerDisplayStoreImpl<T>( override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println(perDisplayInstances) } + + private companion object { + const val TAG = "PerDisplayStore" + } } class SingleDisplayStore<T>(defaultInstance: T) : PerDisplayStore<T> { diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt index 22e467bd5e3c..99c9ca98bae9 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt @@ -33,7 +33,7 @@ interface DisplayWindowPropertiesInteractor { * * @throws IllegalArgumentException if no display with the given display id exists. */ - fun getForStatusBar(displayId: Int): DisplayWindowProperties + fun getForStatusBar(displayId: Int): DisplayWindowProperties? } @SysUISingleton @@ -42,7 +42,7 @@ class DisplayWindowPropertiesInteractorImpl constructor(private val repo: DisplayWindowPropertiesRepository) : DisplayWindowPropertiesInteractor { - override fun getForStatusBar(displayId: Int): DisplayWindowProperties { + override fun getForStatusBar(displayId: Int): DisplayWindowProperties? { return repo.get(displayId, TYPE_STATUS_BAR) } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index c895732f79f6..f9df67661b49 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -720,9 +720,24 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView); - mViewCaptureAwareWindowManager.addView(mFrame, - getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration - .getRotation())); + try { + mViewCaptureAwareWindowManager.addView( + mFrame, + getBarLayoutParams( + mContext.getResources() + .getConfiguration() + .windowConfiguration + .getRotation())); + } catch (WindowManager.InvalidDisplayException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + Log.e( + TAG, + "Unable to add view to WindowManager. Display with id " + + mDisplayId + + " does not exist anymore", + e); + } mDisplayId = mContext.getDisplayId(); mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId(); @@ -764,6 +779,15 @@ public class NavigationBar extends ViewController<NavigationBarView> implements Trace.beginSection("NavigationBar#removeViewImmediate"); try { mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView()); + } catch (IllegalArgumentException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + // When that happens, adding the View to WindowManager fails, and therefore removing + // it here will fail too, since it wasn't added in the first place. + Log.e( + TAG, + "Failed to removed view from WindowManager. The View wasn't attached.", + e); } finally { Trace.endSection(); } @@ -859,7 +883,15 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mOrientationHandle != null) { resetSecondaryHandle(); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); - mViewCaptureAwareWindowManager.removeView(mOrientationHandle); + try { + mViewCaptureAwareWindowManager.removeView(mOrientationHandle); + } catch (IllegalArgumentException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + // When that happens, adding the View to WindowManager fails, and therefore removing + // it here will fail too, since it wasn't added in the first place. + Log.e(TAG, "Trying to remove a View that is not attached", e); + } mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } @@ -930,7 +962,18 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId()); mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; - mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams); + try { + mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams); + } catch (WindowManager.InvalidDisplayException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + Log.e( + TAG, + "Unable to add view to WindowManager. Display with id " + + mDisplayId + + " does not exist anymore", + e); + } mOrientationHandle.setVisibility(View.GONE); logNavbarOrientation("initSecondaryHomeHandleForRotation"); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index 1c232e9ddced..3696b133000c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -22,7 +22,6 @@ import android.net.Uri import android.util.Log import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.UiEventLogger -import com.android.systemui.Flags.screenshotContextUrl import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R import com.android.systemui.screenshot.ActionIntentCreator.createEdit @@ -33,6 +32,7 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance import com.android.systemui.screenshot.ui.viewmodel.PreviewAction +import com.android.systemui.shared.Flags.screenshotContextUrl import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt index 201dc0339a0a..4edba2785e89 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt @@ -77,7 +77,17 @@ constructor( private fun getContextOrDefault(displayId: Int): Context { return try { traceSection({ "Getting dialog context for displayId=$displayId" }) { - displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context + val displayWindowProperties = + displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE) + if (displayWindowProperties == null) { + Log.e( + TAG, + "DisplayWindowPropertiesRepository returned null for display $displayId. Returning default one", + ) + defaultContext + } else { + displayWindowProperties.context + } } } catch (e: Exception) { // This can happen if the display was disconnected in the meantime. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt index d24eddaf321f..d25ca285c53b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt @@ -72,7 +72,7 @@ constructor( private fun initializeStatusBarForDisplay(displayId: Int, result: RegisterStatusBarResult) { if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) { - statusBarModeRepository.forDisplay(displayId).showTransient() + statusBarModeRepository.forDisplay(displayId)?.showTransient() } val commandQueueCallbacks = commandQueueCallbacksLazy.get() commandQueueCallbacks.onSystemBarAttributesChanged( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt index 9e9a38e87924..b057fb0433fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt @@ -89,21 +89,26 @@ constructor( } private fun createAndStartOrchestratorForDisplay(displayId: Int) { + val statusBarModeRepository = statusBarModeRepositoryStore.forDisplay(displayId) ?: return + val statusBarInitializer = initializerStore.forDisplay(displayId) ?: return + val statusBarWindowController = + statusBarWindowControllerStore.forDisplay(displayId) ?: return + val autoHideController = autoHideControllerStore.forDisplay(displayId) ?: return statusBarOrchestratorFactory .create( displayId, displayScopeRepository.scopeForDisplay(displayId), statusBarWindowStateRepositoryStore.forDisplay(displayId), - statusBarModeRepositoryStore.forDisplay(displayId), - initializerStore.forDisplay(displayId), - statusBarWindowControllerStore.forDisplay(displayId), - autoHideControllerStore.forDisplay(displayId), + statusBarModeRepository, + statusBarInitializer, + statusBarWindowController, + autoHideController, ) .start() } private fun createAndStartInitializerForDisplay(displayId: Int) { - statusBarInitializerStore.forDisplay(displayId).start() + statusBarInitializerStore.forDisplay(displayId)?.start() } private fun startPrivacyDotForDisplay(displayId: Int) { @@ -111,6 +116,6 @@ constructor( // For the default display, privacy dot is started via ScreenDecorations return } - privacyDotWindowControllerStore.forDisplay(displayId).start() + privacyDotWindowControllerStore.forDisplay(displayId)?.start() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 4c54fc49e536..1e127ee054e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -20,9 +20,11 @@ import android.view.ViewGroup import androidx.annotation.VisibleForTesting import com.android.systemui.CoreStartable import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarView @@ -34,7 +36,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import java.lang.IllegalStateException import javax.inject.Provider /** @@ -75,6 +76,8 @@ interface StatusBarInitializer : CoreStartable { fun create( statusBarWindowController: StatusBarWindowController, statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, + statusBarConfigurationController: StatusBarConfigurationController, + darkIconDispatcher: DarkIconDispatcher, ): StatusBarInitializer } } @@ -84,6 +87,8 @@ class StatusBarInitializerImpl constructor( @Assisted private val statusBarWindowController: StatusBarWindowController, @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, + @Assisted private val statusBarConfigurationController: StatusBarConfigurationController, + @Assisted private val darkIconDispatcher: DarkIconDispatcher, private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, private val statusBarRootFactory: StatusBarRootFactory, private val componentFactory: HomeStatusBarComponent.Factory, @@ -131,23 +136,32 @@ constructor( -> val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar) component = - componentFactory.create(phoneStatusBarView).also { component -> - // CollapsedStatusBarFragment used to be responsible initializing - component.init() - - statusBarViewUpdatedListener?.onStatusBarViewUpdated( - component.phoneStatusBarViewController, - component.phoneStatusBarTransitions, + componentFactory + .create( + phoneStatusBarView, + statusBarConfigurationController, + statusBarWindowController, + darkIconDispatcher, ) - - if (StatusBarConnectedDisplays.isEnabled) { - statusBarModePerDisplayRepository.onStatusBarViewInitialized(component) - } else { - creationListeners.forEach { listener -> - listener.onStatusBarViewInitialized(component) + .also { component -> + // CollapsedStatusBarFragment used to be responsible initializing + component.init() + + statusBarViewUpdatedListener?.onStatusBarViewUpdated( + component.phoneStatusBarViewController, + component.phoneStatusBarTransitions, + ) + + if (StatusBarConnectedDisplays.isEnabled) { + statusBarModePerDisplayRepository.onStatusBarViewInitialized( + component + ) + } else { + creationListeners.forEach { listener -> + listener.onStatusBarViewInitialized(component) + } } } - } } // Add the new compose view to the hierarchy because we don't use fragment transactions @@ -163,9 +177,11 @@ constructor( CollapsedStatusBarFragment.TAG, object : FragmentHostManager.FragmentListener { override fun onFragmentViewCreated(tag: String, fragment: Fragment) { - component = - (fragment as CollapsedStatusBarFragment).homeStatusBarComponent - ?: throw IllegalStateException() + val statusBarFragment = fragment as CollapsedStatusBarFragment + if (statusBarFragment.homeStatusBarComponent == null) { + return + } + component = fragment.homeStatusBarComponent statusBarViewUpdatedListener?.onStatusBarViewUpdated( component!!.phoneStatusBarViewController, component!!.phoneStatusBarTransitions, @@ -195,6 +211,8 @@ constructor( override fun create( statusBarWindowController: StatusBarWindowController, statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, + statusBarConfigurationController: StatusBarConfigurationController, + darkIconDispatcher: DarkIconDispatcher, ): StatusBarInitializerImpl } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt index 4f815c1f0b31..de6cd072afd7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt @@ -22,6 +22,8 @@ import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.PerDisplayStore import com.android.systemui.display.data.repository.PerDisplayStoreImpl import com.android.systemui.display.data.repository.SingleDisplayStore +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject @@ -39,6 +41,8 @@ constructor( private val factory: StatusBarInitializer.Factory, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, + private val darkIconDispatcherStore: DarkIconDispatcherStore, ) : StatusBarInitializerStore, PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) { @@ -47,10 +51,19 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer { + override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer? { + val statusBarWindowController = + statusBarWindowControllerStore.forDisplay(displayId) ?: return null + val statusBarModePerDisplayRepository = + statusBarModeRepositoryStore.forDisplay(displayId) ?: return null + val statusBarConfigurationController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null + val darkIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) ?: return null return factory.create( - statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId), - statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId), + statusBarWindowController, + statusBarModePerDisplayRepository, + statusBarConfigurationController, + darkIconDispatcher, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt index 8183a487cee2..041f3c816149 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt @@ -65,8 +65,9 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher { - val properties = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) + override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher? { + val properties = + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null return factory.create(displayId, properties.context) } @@ -103,7 +104,7 @@ constructor(private val store: SysuiDarkIconDispatcherStore) : DarkIconDispatche override val defaultDisplay: DarkIconDispatcher get() = store.defaultDisplay - override fun forDisplay(displayId: Int): DarkIconDispatcher = store.forDisplay(displayId) + override fun forDisplay(displayId: Int): DarkIconDispatcher? = store.forDisplay(displayId) } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt index e4987555833b..c629d10b90b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt @@ -49,13 +49,16 @@ constructor( LightBarControllerStore, PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) { - override fun createInstanceForDisplay(displayId: Int): LightBarController { + override fun createInstanceForDisplay(displayId: Int): LightBarController? { + val darkIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) ?: return null + val statusBarModePerDisplayRepository = + statusBarModeRepositoryStore.forDisplay(displayId) ?: return null return factory .create( displayId, displayScopeRepository.scopeForDisplay(displayId), - darkIconDispatcherStore.forDisplay(displayId), - statusBarModeRepositoryStore.forDisplay(displayId), + darkIconDispatcher, + statusBarModePerDisplayRepository, ) .also { it.start() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt index bd61c44e3d9b..d48c94bd0893 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt @@ -52,11 +52,14 @@ constructor( PrivacyDotViewControllerStore, PerDisplayStoreImpl<PrivacyDotViewController>(backgroundApplicationScope, displayRepository) { - override fun createInstanceForDisplay(displayId: Int): PrivacyDotViewController { + override fun createInstanceForDisplay(displayId: Int): PrivacyDotViewController? { + val configurationController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null + val contentInsetsProvider = contentInsetsProviderStore.forDisplay(displayId) ?: return null return factory.create( displayScopeRepository.scopeForDisplay(displayId), - statusBarConfigurationControllerStore.forDisplay(displayId), - contentInsetsProviderStore.forDisplay(displayId), + configurationController, + contentInsetsProvider, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt index a1f56552629b..086cc99957ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt @@ -58,15 +58,18 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController { + override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController? { if (displayId == Display.DEFAULT_DISPLAY) { throw IllegalArgumentException("This class should only be used for connected displays") } val displayWindowProperties = displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL) + ?: return null + val privacyDotViewController = + privacyDotViewControllerStore.forDisplay(displayId) ?: return null return windowControllerFactory.create( displayId = displayId, - privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId), + privacyDotViewController = privacyDotViewController, viewCaptureAwareWindowManager = viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager), inflater = displayWindowProperties.layoutInflater, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt index 6cf2c73a7138..38cea832ad76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt @@ -62,9 +62,9 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationController { + override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationController? { val displayWindowProperties = - displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null return configurationControllerFactory.create(displayWindowProperties.context) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt index e471b12c1d58..554c46f6c219 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt @@ -59,13 +59,17 @@ constructor( displayRepository, ) { - override fun createInstanceForDisplay(displayId: Int): StatusBarContentInsetsProvider { - val context = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context + override fun createInstanceForDisplay(displayId: Int): StatusBarContentInsetsProvider? { + val displayWindowProperties = + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null + val context = displayWindowProperties.context + val configurationController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null val cameraProtectionLoader = cameraProtectionLoaderFactory.create(context) return factory .create( context, - statusBarConfigurationControllerStore.forDisplay(displayId), + configurationController, sysUICutoutProviderFactory.create(context, cameraProtectionLoader), ) .also { it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt index 7760f58805c9..ffc125539521 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt @@ -62,11 +62,17 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController { + override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController? { + val displayWindowProperties = + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null + val statusBarWindowController = + statusBarWindowControllerStore.forDisplay(displayId) ?: return null + val contentInsetsProvider = + statusBarContentInsetsProviderStore.forDisplay(displayId) ?: return null return factory.create( - displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context, - statusBarWindowControllerStore.forDisplay(displayId), - statusBarContentInsetsProviderStore.forDisplay(displayId), + displayWindowProperties.context, + statusBarWindowController, + contentInsetsProvider, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt index f2bb7b16439d..4b9721ea4fe5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt @@ -72,5 +72,5 @@ constructor( } private fun controllersForAllDisplays() = - displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) } + displayRepository.displays.value.mapNotNull { controllerStore.forDisplay(it.displayId) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt index 9928ac67f185..f7799bb75ae0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.events +import android.util.Log import android.view.Display import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT @@ -23,6 +24,7 @@ import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.LayoutInflater import android.view.View +import android.view.WindowManager.InvalidDisplayException import android.view.WindowManager.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout import com.android.app.viewcapture.ViewCaptureAwareWindowManager @@ -97,7 +99,17 @@ constructor( // PrivacyDotViewController expects the dot view to have a FrameLayout parent. val rootView = FrameLayout(context) rootView.addView(this) - viewCaptureAwareWindowManager.addView(rootView, params) + try { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + viewCaptureAwareWindowManager.addView(rootView, params) + } catch (e: InvalidDisplayException) { + Log.e( + TAG, + "Unable to add view to WM. Display with id $displayId does not exist anymore", + e, + ) + } } @AssistedFactory @@ -109,4 +121,8 @@ constructor( inflater: LayoutInflater, ): PrivacyDotWindowController } + + private companion object { + const val TAG = "PrivacyDotWindowController" + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt index 3e854b4dbaf8..2aea7d85e01a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt @@ -78,7 +78,13 @@ private fun toPopupChipModel(model: MediaControlChipModel?, context: Context): P res = com.android.internal.R.drawable.ic_audio_media, contentDescription = contentDescription, ), + hoverIcon = + Icon.Resource( + res = com.android.internal.R.drawable.ic_media_pause, + contentDescription = null, + ), chipText = model.songName.toString(), + isToggled = false, // TODO(b/385202114): Show a popup containing the media carousal when the chip is toggled. onToggle = {}, // TODO(b/385202193): Add support for clicking on the icon on a media chip. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt index 0a6c4d0fd14f..e7e3d02ae4c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt @@ -38,7 +38,13 @@ sealed class PopupChipModel { data class Shown( override val chipId: PopupChipId, + /** Default icon displayed on the chip */ val icon: Icon, + /** + * Icon to be displayed if the chip is hovered. i.e. the mouse pointer is inside the bounds + * of the chip. + */ + val hoverIcon: Icon, val chipText: String, val isToggled: Boolean = false, val onToggle: () -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt new file mode 100644 index 000000000000..1a775d71983c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.featurepods.popups.ui.compose + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel + +/** + * A clickable chip that can show an anchored popup containing relevant system controls. The chip + * can show an icon that can have its own separate action distinct from its parent chip. Moreover, + * the chip can show text containing contextual information. + */ +@Composable +fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifier) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isToggled = model.isToggled + + Surface( + shape = RoundedCornerShape(16.dp), + modifier = + modifier + .hoverable(interactionSource = interactionSource) + .padding(vertical = 4.dp) + .widthIn(max = 120.dp) + .animateContentSize() + .clickable(onClick = { model.onToggle() }), + color = + if (isToggled) { + MaterialTheme.colorScheme.primaryContainer + } else { + MaterialTheme.colorScheme.surfaceContainerHighest + }, + ) { + Row( + modifier = Modifier.padding(start = 4.dp, end = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + val currentIcon = if (isHovered) model.hoverIcon else model.icon + val backgroundColor = + if (isToggled) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.primaryContainer + } + + Icon( + icon = currentIcon, + modifier = + Modifier.background(color = backgroundColor, shape = CircleShape) + .clickable( + role = Role.Button, + onClick = model.onIconPressed, + indication = ripple(), + interactionSource = remember { MutableInteractionSource() }, + ) + .padding(2.dp) + .size(18.dp), + tint = contentColorFor(backgroundColor), + ) + + Text( + text = model.chipText, + style = MaterialTheme.typography.labelLarge, + softWrap = false, + overflow = TextOverflow.Ellipsis, + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt index 56bbd74af1c2..d35674d8dd9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt @@ -18,10 +18,11 @@ package com.android.systemui.statusbar.featurepods.popups.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel /** Container view that holds all right hand side chips in the status bar. */ @@ -29,9 +30,11 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Modifier = Modifier) { // TODO(b/385353140): Add padding and spacing for this container according to UX specs. Box { - Row(verticalAlignment = Alignment.CenterVertically) { - // TODO(b/385352859): Show `StatusBarPopupChip` here instead of `Text` once it is ready. - chips.forEach { chip -> Text(text = chip.chipText) } + Row( + modifier = Modifier.padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + chips.forEach { chip -> StatusBarPopupChip(chip) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt index 227a1fefb982..eb55856d994b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt @@ -62,8 +62,10 @@ constructor( override fun iconView(key: String): StatusBarIconView? { val entry = notifCollection.getEntry(key) ?: return null + val displayWindowProperties = + displayWindowPropertiesInteractor.getForStatusBar(displayId) ?: return null return cachedIcons.computeIfAbsent(key) { - val context = displayWindowPropertiesInteractor.getForStatusBar(displayId).context + val context = displayWindowProperties.context iconManager.createSbIconView(context, entry) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 2ba28a660116..e1032820fb71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -68,13 +68,14 @@ constructor( .distinctUntilChanged() /** The colors with which to display the notification icons. */ - fun iconColors(displayId: Int): Flow<NotificationIconColors> = - darkIconInteractor + fun iconColors(displayId: Int): Flow<NotificationIconColors> { + return darkIconInteractor .darkState(displayId) .map { (areas: Collection<Rect>, tint: Int) -> IconColorsImpl(tint, areas) } .flowOn(bgContext) .conflate() .distinctUntilChanged() + } /** [NotificationIconsViewData] indicating which icons to display in the view. */ val icons: Flow<NotificationIconsViewData> = 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 5a52c379d0d6..7e3d0043b91a 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 @@ -3685,6 +3685,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) { nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry); } + // Note: nowExpanded is going to be true here on the first expansion of minimized groups, + // even though the group itself is not expanded. Use mGroupExpansionManager to get the real + // group expansion if needed. if (nowExpanded != wasExpanded) { updateShelfIconColor(); if (mLogger != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 70e27a981b49..7c44eae6c0b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.Flags.notificationsRedesignTemplates; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; @@ -479,15 +481,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entryForLogging, "creating low-priority group summary remote view"); result.mNewMinimizedGroupHeaderView = - builder.makeLowPriorityContentView(true /* useRegularSubtext */); + builder.makeLowPriorityContentView(/* useRegularSubtext = */ true, + /* highlightExpander = */ notificationsRedesignTemplates()); } } setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider); result.packageContext = packageContext; result.headsUpStatusBarText = builder.getHeadsUpStatusBarText( - false /* showingPublic */); + /* showingPublic = */ false); result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( - true /* showingPublic */); + /* showingPublic = */ true); return result; }); @@ -1136,7 +1139,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static RemoteViews createContentView(Notification.Builder builder, boolean isMinimized, boolean useLarge) { if (isMinimized) { - return builder.makeLowPriorityContentView(false /* useRegularSubtext */); + return builder.makeLowPriorityContentView(/* useRegularSubtext = */ false, + /* highlightExpander = */ false); } return builder.createContentView(useLarge); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index c619b17f1ad8..ae9b69c8f6bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row import android.annotation.SuppressLint +import android.app.Flags.notificationsRedesignTemplates import android.app.Notification import android.app.Notification.MessagingStyle import android.content.Context @@ -887,7 +888,10 @@ constructor( entryForLogging, "creating low-priority group summary remote view", ) - builder.makeLowPriorityContentView(true /* useRegularSubtext */) + builder.makeLowPriorityContentView( + /* useRegularSubtext = */ true, + /* highlightExpander = */ notificationsRedesignTemplates(), + ) } else null NewRemoteViews( contracted = contracted, @@ -1657,7 +1661,10 @@ constructor( useLarge: Boolean, ): RemoteViews { return if (isMinimized) { - builder.makeLowPriorityContentView(false /* useRegularSubtext */) + builder.makeLowPriorityContentView( + /* useRegularSubtext = */ false, + /* highlightExpander = */ false, + ) } else builder.createContentView(useLarge) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 9fb7fad52bb6..e477c7430262 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -30,7 +30,6 @@ import android.os.Trace; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.NotificationHeaderView; import android.view.View; @@ -93,9 +92,16 @@ public class NotificationChildrenContainer extends ViewGroup private int mChildPadding; private int mDividerHeight; private float mDividerAlpha; - private int mNotificationHeaderMargin; - private int mNotificationTopPadding; + private int mHeaderHeight; + /** Margin needed at the top in the collapsed group, to allow space for the header text. */ + private int mCollapsedHeaderMargin; + /** + * Spacing needed in addition to {@link this#mCollapsedHeaderMargin} when the group is expanded, + * to accommodate the full header. It doesn't include the spacing needed for the first divider. + */ + private int mAdditionalExpandedHeaderMargin; + private float mCollapsedBottomPadding; private boolean mChildrenExpanded; private ExpandableNotificationRow mContainingNotification; @@ -105,7 +111,6 @@ public class NotificationChildrenContainer extends ViewGroup private boolean mUserLocked; private int mActualHeight; private boolean mNeverAppliedGroupState; - private int mHeaderHeight; /** * Whether or not individual notifications that are part of this container will have shadows. @@ -168,15 +173,18 @@ public class NotificationChildrenContainer extends ViewGroup mDividerHeight = res.getDimensionPixelOffset( R.dimen.notification_children_container_divider_height); mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha); - mNotificationHeaderMargin = notificationsRedesignTemplates() - ? Notification.Builder.getContentMarginTop(getContext(), - R.dimen.notification_2025_children_container_margin_top) - : res.getDimensionPixelOffset(R.dimen.notification_children_container_margin_top); - mNotificationTopPadding = res.getDimensionPixelOffset( - R.dimen.notification_children_container_top_padding); - mHeaderHeight = notificationsRedesignTemplates() - ? res.getDimensionPixelSize(R.dimen.notification_2025_header_height) - : mNotificationHeaderMargin + mNotificationTopPadding; + if (notificationsRedesignTemplates()) { + mHeaderHeight = res.getDimensionPixelSize(R.dimen.notification_2025_header_height); + mCollapsedHeaderMargin = Notification.Builder.getContentMarginTop(getContext(), + R.dimen.notification_2025_children_container_margin_top); + mAdditionalExpandedHeaderMargin = mHeaderHeight - mCollapsedHeaderMargin; + } else { + mCollapsedHeaderMargin = res.getDimensionPixelOffset( + R.dimen.notification_children_container_margin_top); + mAdditionalExpandedHeaderMargin = res.getDimensionPixelOffset( + R.dimen.notification_children_container_top_padding); + mHeaderHeight = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin; + } mCollapsedBottomPadding = res.getDimensionPixelOffset( R.dimen.notification_children_collapsed_bottom_padding); mEnableShadowOnChildNotifications = @@ -189,7 +197,7 @@ public class NotificationChildrenContainer extends ViewGroup res.getBoolean(R.bool.config_hideDividersDuringExpand); mTranslationForHeader = res.getDimensionPixelOffset( com.android.internal.R.dimen.notification_content_margin) - - mNotificationHeaderMargin; + - mCollapsedHeaderMargin; mHybridGroupManager.initDimens(); mMinSingleLineHeight = getResources().getDimensionPixelSize( R.dimen.conversation_single_line_face_pile_size); @@ -251,7 +259,7 @@ public class NotificationChildrenContainer extends ViewGroup newHeightSpec); } int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); - int height = mNotificationHeaderMargin + mNotificationTopPadding; + int height = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin; int childCount = Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); @@ -560,7 +568,8 @@ public class NotificationChildrenContainer extends ViewGroup builder = Notification.Builder.recoverBuilder(getContext(), notification.getNotification()); } - header = builder.makeLowPriorityContentView(true /* useRegularSubtext */); + header = builder.makeLowPriorityContentView(true /* useRegularSubtext */, + notificationsRedesignTemplates() /* highlightExpander */); if (mMinimizedGroupHeader == null) { mMinimizedGroupHeader = (NotificationHeaderView) header.apply(getContext(), this); @@ -704,7 +713,7 @@ public class NotificationChildrenContainer extends ViewGroup return mMinimizedGroupHeader.getHeight(); } } - int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; + int intrinsicHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation; int visibleChildren = 0; int childCount = mAttachedChildren.size(); boolean firstChild = true; @@ -728,11 +737,11 @@ public class NotificationChildrenContainer extends ViewGroup if (mUserLocked) { intrinsicHeight += NotificationUtils.interpolate( 0, - mNotificationTopPadding + mDividerHeight, + mAdditionalExpandedHeaderMargin + mDividerHeight, expandFactor); } else { intrinsicHeight += childrenExpanded - ? mNotificationTopPadding + mDividerHeight + ? mAdditionalExpandedHeaderMargin + mDividerHeight : 0; } firstChild = false; @@ -757,7 +766,7 @@ public class NotificationChildrenContainer extends ViewGroup */ public void updateState(ExpandableViewState parentState) { int childCount = mAttachedChildren.size(); - int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation; + int yPosition = mCollapsedHeaderMargin + mCurrentHeaderTranslation; boolean firstChild = true; int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); int lastVisibleIndex = maxAllowedVisibleChildren - 1; @@ -785,10 +794,12 @@ public class NotificationChildrenContainer extends ViewGroup if (expandingToExpandedGroup) { yPosition += NotificationUtils.interpolate( 0, - mNotificationTopPadding + mDividerHeight, + mAdditionalExpandedHeaderMargin + mDividerHeight, expandFactor); } else { - yPosition += mChildrenExpanded ? mNotificationTopPadding + mDividerHeight : 0; + yPosition += mChildrenExpanded + ? mAdditionalExpandedHeaderMargin + mDividerHeight + : 0; } firstChild = false; } @@ -851,7 +862,7 @@ public class NotificationChildrenContainer extends ViewGroup } } else { mGroupOverFlowState.setYTranslation( - mGroupOverFlowState.getYTranslation() + mNotificationHeaderMargin); + mGroupOverFlowState.getYTranslation() + mCollapsedHeaderMargin); mGroupOverFlowState.setAlpha(0.0f); } } @@ -1147,10 +1158,12 @@ public class NotificationChildrenContainer extends ViewGroup return mContainingNotification; } + @Nullable public NotificationViewWrapper getNotificationViewWrapper() { return mGroupHeaderWrapper; } + @Nullable public NotificationViewWrapper getMinimizedGroupHeaderWrapper() { return mMinimizedGroupHeaderWrapper; } @@ -1160,10 +1173,12 @@ public class NotificationChildrenContainer extends ViewGroup return mCurrentHeader; } + @Nullable public NotificationHeaderView getGroupHeader() { return mGroupHeader; } + @Nullable public NotificationHeaderView getMinimizedNotificationHeader() { return mMinimizedGroupHeader; } @@ -1300,8 +1315,8 @@ public class NotificationChildrenContainer extends ViewGroup return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true /* likeHighPriority */); } - int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation - + mNotificationTopPadding; + int maxContentHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation + + mAdditionalExpandedHeaderMargin; int visibleChildren = 0; int childCount = mAttachedChildren.size(); for (int i = 0; i < childCount; i++) { @@ -1363,8 +1378,8 @@ public class NotificationChildrenContainer extends ViewGroup } private int getVisibleChildrenExpandHeight() { - int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation - + mNotificationTopPadding + mDividerHeight; + int intrinsicHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation + + mAdditionalExpandedHeaderMargin + mDividerHeight; int visibleChildren = 0; int childCount = mAttachedChildren.size(); int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); @@ -1429,7 +1444,7 @@ public class NotificationChildrenContainer extends ViewGroup } return mMinimizedGroupHeader.getHeight(); } - int minExpandHeight = mNotificationHeaderMargin + headerTranslation; + int minExpandHeight = mCollapsedHeaderMargin + headerTranslation; int visibleChildren = 0; boolean firstChild = true; int childCount = mAttachedChildren.size(); @@ -1517,18 +1532,13 @@ public class NotificationChildrenContainer extends ViewGroup // The overflow number is not used, so its color is irrelevant; skip this return; } - int color = mContainingNotification.getNotificationColor(); - Resources.Theme theme = new ContextThemeWrapper(mContext, - com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); - - color = mContext.getColor(com.android.internal.R.color.materialColorPrimary); - + int color = mContext.getColor(com.android.internal.R.color.materialColorPrimary); mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); } public int getPositionInLinearLayout(View childInGroup) { - int position = mNotificationHeaderMargin + mCurrentHeaderTranslation - + mNotificationTopPadding; + int position = mCollapsedHeaderMargin + mCurrentHeaderTranslation + + mAdditionalExpandedHeaderMargin; for (int i = 0; i < mAttachedChildren.size(); i++) { ExpandableNotificationRow child = mAttachedChildren.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 50e5a2352f43..9766f9e0ab1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack; +import static android.app.Flags.notificationsRedesignTemplates; import static android.os.Trace.TRACE_TAG_APP; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_UP; @@ -62,6 +63,7 @@ import android.view.DisplayCutout; import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.NotificationHeaderView; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -6849,15 +6851,29 @@ public class NotificationStackScrollLayout mExpandedGroupView = changedRow; mNeedsAnimation = true; } + changedRow.setChildrenExpanded(expanded); onChildHeightChanged(changedRow, false /* needsAnimation */); + updateGroupHeaderAlignment(changedRow, expanded); - runAfterAnimationFinished(new Runnable() { - @Override - public void run() { - changedRow.onFinishedExpansionChange(); - } - }); + runAfterAnimationFinished(changedRow::onFinishedExpansionChange); + } + + private void updateGroupHeaderAlignment(ExpandableNotificationRow row, boolean expanded) { + if (!notificationsRedesignTemplates()) { + return; + } + + NotificationChildrenContainer childrenContainer = row.getChildrenContainer(); + if (childrenContainer == null) { + Log.wtf(TAG, "Tried to update group header alignment for something that's " + + "not a group; key = " + row.getEntry().getKey()); + return; + } + NotificationHeaderView header = childrenContainer.getGroupHeader(); + if (header != null) { + header.centerTopLine(expanded); + } } private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt index 744f96918eef..2ae38dd488bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt @@ -47,9 +47,9 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): AutoHideController { + override fun createInstanceForDisplay(displayId: Int): AutoHideController? { val displayWindowProperties = - displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) + displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) ?: return null return autoHideControllerFactory.create(displayWindowProperties.context) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java index ea67f1cdb60a..ca0c1ac9ce7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java @@ -499,9 +499,9 @@ public class LightBarControllerImpl implements /** Creates a {@link LightBarControllerImpl}. */ LightBarControllerImpl create( int displayId, - CoroutineScope coroutineScope, - DarkIconDispatcher darkIconDispatcher, - StatusBarModePerDisplayRepository statusBarModePerDisplayRepository); + @NonNull CoroutineScope coroutineScope, + @NonNull DarkIconDispatcher darkIconDispatcher, + @NonNull StatusBarModePerDisplayRepository statusBarModePerDisplayRepository); } public static class LegacyFactory implements LightBarController.Factory { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt index 394502b2d31f..031754db3c57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt @@ -33,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange @@ -54,7 +55,7 @@ constructor( ) { /** Creates listener always using the same light color for overlay */ - fun createListener(view: View) = + fun createListener(view: View): StatusOverlayHoverListener = StatusOverlayHoverListener( view, configurationController, @@ -65,8 +66,10 @@ constructor( /** * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay */ - fun createDarkAwareListener(view: View) = - createDarkAwareListener(view, view.darkIconDispatcher.darkChangeFlow()) + fun createDarkAwareListener(view: View): StatusOverlayHoverListener? { + val darkIconDispatcher = view.darkIconDispatcher ?: return null + return createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow()) + } /** * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay @@ -78,27 +81,34 @@ constructor( rightHoverMargin: Int = 0, topHoverMargin: Int = 0, bottomHoverMargin: Int = 0, - ) = - createDarkAwareListener( + ): StatusOverlayHoverListener? { + val darkIconDispatcher = view.darkIconDispatcher ?: return null + return createDarkAwareListener( view, - view.darkIconDispatcher.darkChangeFlow(), + darkIconDispatcher.darkChangeFlow(), leftHoverMargin, rightHoverMargin, topHoverMargin, bottomHoverMargin, ) + } /** * Creates listener using provided [DarkChange] producer to determine light or dark color of the * overlay */ - fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) = - StatusOverlayHoverListener( + fun createDarkAwareListener( + view: View, + darkFlow: StateFlow<DarkChange>, + ): StatusOverlayHoverListener? { + val configurationController = view.statusBarConfigurationController ?: return null + return StatusOverlayHoverListener( view, - view.statusBarConfigurationController, + configurationController, view.resources, darkFlow.map { toHoverTheme(view, it) }, ) + } private fun createDarkAwareListener( view: View, @@ -107,10 +117,11 @@ constructor( rightHoverMargin: Int = 0, topHoverMargin: Int = 0, bottomHoverMargin: Int = 0, - ) = - StatusOverlayHoverListener( + ): StatusOverlayHoverListener? { + val configurationController = view.statusBarConfigurationController ?: return null + return StatusOverlayHoverListener( view, - view.statusBarConfigurationController, + configurationController, view.resources, darkFlow.map { toHoverTheme(view, it) }, leftHoverMargin, @@ -118,11 +129,12 @@ constructor( topHoverMargin, bottomHoverMargin, ) + } - private val View.statusBarConfigurationController + private val View.statusBarConfigurationController: StatusBarConfigurationController? get() = statusBarConfigurationControllerStore.forDisplay(context.displayId) - private val View.darkIconDispatcher + private val View.darkIconDispatcher: SysuiDarkIconDispatcher? get() = darkIconDispatcherStore.forDisplay(context.displayId) private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index 4f32aaa2654e..037dda91fb00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -31,8 +31,10 @@ import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator import com.android.systemui.statusbar.core.StatusBarRootModernization +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule import com.android.systemui.statusbar.phone.AutoHideControllerStore @@ -107,10 +109,14 @@ interface StatusBarPhoneModule { implFactory: StatusBarInitializerImpl.Factory, statusBarWindowControllerStore: StatusBarWindowControllerStore, statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, + darkIconDispatcherStore: DarkIconDispatcherStore, ): StatusBarInitializerImpl { return implFactory.create( statusBarWindowControllerStore.defaultDisplay, statusBarModeRepositoryStore.defaultDisplay, + statusBarConfigurationControllerStore.defaultDisplay, + darkIconDispatcherStore.defaultDisplay, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt index 49356eba2842..04646549bfdc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt @@ -15,12 +15,14 @@ */ package com.android.systemui.statusbar.phone.data.repository +import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange import dagger.Binds import dagger.Module import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow /** Dark-mode state for tinting icons. */ @@ -33,8 +35,22 @@ class DarkIconRepositoryImpl @Inject constructor(private val darkIconDispatcherStore: SysuiDarkIconDispatcherStore) : DarkIconRepository { - override fun darkState(displayId: Int): StateFlow<DarkChange> = - darkIconDispatcherStore.forDisplay(displayId).darkChangeFlow() + override fun darkState(displayId: Int): StateFlow<DarkChange> { + val perDisplayDakIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) + if (perDisplayDakIconDispatcher == null) { + Log.e( + TAG, + "DarkIconDispatcher for display $displayId is null. Returning flow of " + + "DarkChange.EMPTY", + ) + return MutableStateFlow(DarkChange.EMPTY) + } + return perDisplayDakIconDispatcher.darkChangeFlow() + } + + private companion object { + const val TAG = "DarkIconRepositoryImpl" + } } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt index ed8b3e8922f3..b15fffb8dd29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt @@ -34,8 +34,8 @@ class LightsOutInteractor @Inject constructor(private val repository: StatusBarModeRepositoryStore) { - fun isLowProfile(displayId: Int): Flow<Boolean> = - repository.forDisplay(displayId).statusBarMode.map { + fun isLowProfile(displayId: Int): Flow<Boolean>? = + repository.forDisplay(displayId)?.statusBarMode?.map { when (it) { StatusBarMode.LIGHTS_OUT, StatusBarMode.LIGHTS_OUT_TRANSPARENT -> true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index d257288637df..c31e34c50b06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -47,6 +47,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -59,6 +60,9 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarConnectedDisplays; import com.android.systemui.statusbar.core.StatusBarRootModernization; +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; @@ -78,6 +82,8 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarVie import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -156,6 +162,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final NotificationIconContainerStatusBarViewBinder mNicViewBinder; private final DemoModeController mDemoModeController; + private final StatusBarWindowControllerStore mStatusBarWindowControllerStore; + private final StatusBarConfigurationControllerStore mStatusBarConfigurationControllerStore; + private final DarkIconDispatcherStore mDarkIconDispatcherStore; private List<String> mBlockedIcons = new ArrayList<>(); private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>(); @@ -263,7 +272,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue DumpManager dumpManager, StatusBarWindowStateController statusBarWindowStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - DemoModeController demoModeController) { + DemoModeController demoModeController, + StatusBarWindowControllerStore statusBarWindowControllerStore, + StatusBarConfigurationControllerStore statusBarConfigurationControllerStore, + DarkIconDispatcherStore darkIconDispatcherStore) { mHomeStatusBarComponentFactory = homeStatusBarComponentFactory; mOngoingCallController = ongoingCallController; mAnimationScheduler = animationScheduler; @@ -287,6 +299,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarWindowStateController = statusBarWindowStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDemoModeController = demoModeController; + mStatusBarWindowControllerStore = statusBarWindowControllerStore; + mStatusBarConfigurationControllerStore = statusBarConfigurationControllerStore; + mDarkIconDispatcherStore = darkIconDispatcherStore; } private final DemoMode mDemoModeCallback = new DemoMode() { @@ -337,8 +352,27 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mDumpManager.registerDumpable(getDumpableName(), this); - mHomeStatusBarComponent = mHomeStatusBarComponentFactory.create( - (PhoneStatusBarView) getView()); + int displayId = view.getContext().getDisplayId(); + StatusBarConfigurationController configurationController = + mStatusBarConfigurationControllerStore.forDisplay(displayId); + if (configurationController == null) { + return; + } + StatusBarWindowController statusBarWindowController = + mStatusBarWindowControllerStore.forDisplay(displayId); + if (statusBarWindowController == null) { + return; + } + DarkIconDispatcher darkIconDispatcher = mDarkIconDispatcherStore.forDisplay(displayId); + if (darkIconDispatcher == null) { + return; + } + mHomeStatusBarComponent = + mHomeStatusBarComponentFactory.create( + (PhoneStatusBarView) getView(), + configurationController, + statusBarWindowController, + darkIconDispatcher); mHomeStatusBarComponent.init(); mStartableStates.clear(); for (Startable startable : mHomeStatusBarComponent.getStartables()) { @@ -453,6 +487,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onResume() { super.onResume(); + if (mHomeStatusBarComponent == null) { + return; + } mCommandQueue.addCallback(this); mStatusBarStateController.addCallback(this); initOngoingCallChip(); @@ -468,6 +505,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onPause() { super.onPause(); + if (mHomeStatusBarComponent == null) { + return; + } mCommandQueue.removeCallback(this); mStatusBarStateController.removeCallback(this); if (!StatusBarRootModernization.isEnabled()) { @@ -480,6 +520,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onDestroyView() { super.onDestroyView(); + if (mHomeStatusBarComponent == null) { + return; + } mStatusBarIconController.removeIconGroup(mDarkIconManager); mCarrierConfigTracker.removeCallback(mCarrierConfigCallback); mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java index f8ad0f2324bc..5837752abdaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java @@ -20,6 +20,7 @@ import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.DisplaySpecific; import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController; @@ -29,6 +30,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarDemoMode; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.window.StatusBarWindowController; import dagger.BindsInstance; import dagger.Subcomponent; @@ -57,7 +59,10 @@ public interface HomeStatusBarComponent { interface Factory { /** */ HomeStatusBarComponent create( - @BindsInstance @RootView PhoneStatusBarView phoneStatusBarView); + @BindsInstance @RootView PhoneStatusBarView phoneStatusBarView, + @BindsInstance StatusBarConfigurationController configurationController, + @BindsInstance StatusBarWindowController statusBarWindowController, + @BindsInstance @DisplaySpecific DarkIconDispatcher darkIconDispatcher); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java index 182f8d7e2fd6..6a331b938cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java @@ -22,19 +22,14 @@ import android.view.ViewStub; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.DisplaySpecific; import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.res.R; import com.android.systemui.statusbar.HeadsUpStatusBarView; -import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore; -import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; -import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarLocation; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import dagger.Module; import dagger.Provides; @@ -149,29 +144,4 @@ public interface HomeStatusBarModule { static int displayId(@RootView PhoneStatusBarView view) { return view.getContext().getDisplayId(); } - - /** */ - @Provides - @HomeStatusBarScope - static StatusBarConfigurationController configurationController( - @DisplaySpecific int displayId, StatusBarConfigurationControllerStore store) { - return store.forDisplay(displayId); - } - - /** */ - @Provides - @HomeStatusBarScope - static StatusBarWindowController provideWindowController( - @DisplaySpecific int displayId, StatusBarWindowControllerStore store) { - return store.forDisplay(displayId); - } - - /** */ - @Provides - @HomeStatusBarScope - @DisplaySpecific - static DarkIconDispatcher darkIconDispatcher( - @DisplaySpecific int displayId, DarkIconDispatcherStore store) { - return store.forDisplay(displayId); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index c3299bbd40e6..7243ba7def58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -70,6 +70,8 @@ constructor( ) { fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView { val composeView = ComposeView(root.context) + val darkIconDispatcher = + darkIconDispatcherStore.forDisplay(root.context.displayId) ?: return composeView composeView.apply { setContent { StatusBarRoot( @@ -80,7 +82,7 @@ constructor( darkIconManagerFactory = darkIconManagerFactory, iconController = iconController, ongoingCallController = ongoingCallController, - darkIconDispatcher = darkIconDispatcherStore.forDisplay(root.context.displayId), + darkIconDispatcher = darkIconDispatcher, eventAnimationInteractor = eventAnimationInteractor, onViewCreated = andThen, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index dcfbc5d6432d..c9cc17389c17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -63,6 +63,7 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -216,7 +217,7 @@ constructor( } else { combine( notificationsInteractor.areAnyNotificationsPresent, - lightsOutInteractor.isLowProfile(displayId), + lightsOutInteractor.isLowProfile(displayId) ?: flowOf(false), ) { hasNotifications, isLowProfile -> hasNotifications && isLowProfile } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index 811a2ec44ccc..848e91d6f896 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -163,7 +163,18 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); Trace.endSection(); - mWindowManager.addView(mStatusBarWindowView, mLp); + try { + mWindowManager.addView(mStatusBarWindowView, mLp); + } catch (WindowManager.InvalidDisplayException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + Log.e( + TAG, + "Unable to add view to WindowManager. Display with id " + + mContext.getDisplayId() + + " doesn't exist anymore.", + e); + } mLpChanged.copyFrom(mLp); mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); @@ -176,7 +187,15 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController public void stop() { StatusBarConnectedDisplays.assertInNewMode(); - mWindowManager.removeView(mStatusBarWindowView); + try { + mWindowManager.removeView(mStatusBarWindowView); + } catch (IllegalArgumentException e) { + // Wrapping this in a try/catch to avoid crashes when a display is instantly removed + // after being added, and initialization hasn't finished yet. + // When that happens, adding the View to WindowManager fails, and therefore removing + // it here will fail too, since it wasn't added in the first place. + Log.e(TAG, "Failed to remove View from WindowManager. View was not attached", e); + } if (StatusBarRootModernization.isEnabled()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt index 74031612f28e..f7688d2feab5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt @@ -54,19 +54,23 @@ constructor( StatusBarConnectedDisplays.assertInNewMode() } - override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController { + override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController? { val statusBarDisplayContext = displayWindowPropertiesRepository.get( displayId = displayId, windowType = WindowManager.LayoutParams.TYPE_STATUS_BAR, - ) + ) ?: return null + val statusBarConfigurationController = + statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null + val contentInsetsProvider = + statusBarContentInsetsProviderStore.forDisplay(displayId) ?: return null val viewCaptureAwareWindowManager = viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager) return controllerFactory.create( statusBarDisplayContext.context, viewCaptureAwareWindowManager, - statusBarConfigurationControllerStore.forDisplay(displayId), - statusBarContentInsetsProviderStore.forDisplay(displayId), + statusBarConfigurationController, + contentInsetsProvider, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 411c81d13e0b..1fcf02d417e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -36,6 +36,8 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_K import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.flowOf; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -54,8 +56,6 @@ import static org.mockito.Mockito.when; import static java.util.Collections.emptySet; -import static kotlinx.coroutines.flow.FlowKt.flowOf; - import android.app.ActivityManager; import android.app.IWallpaperManager; import android.app.NotificationManager; @@ -132,6 +132,7 @@ import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -175,6 +176,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.core.StatusBarConnectedDisplays; import com.android.systemui.statusbar.core.StatusBarInitializerImpl; import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -216,6 +218,10 @@ import com.android.systemui.volume.VolumeComponent; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.StartingSurface; +import dagger.Lazy; + +import kotlinx.coroutines.test.TestScope; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -232,9 +238,6 @@ import java.util.Set; import javax.inject.Provider; -import dagger.Lazy; -import kotlinx.coroutines.test.TestScope; - @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @@ -536,6 +539,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new StatusBarInitializerImpl( mStatusBarWindowController, mStatusBarModePerDisplayRepository, + mock(StatusBarConfigurationController.class), + mock(DarkIconDispatcher.class), mCollapsedStatusBarFragmentProvider, mock(StatusBarRootFactory.class), mock(HomeStatusBarComponent.Factory.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 0b4436755fa5..3a99328fa8ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -61,6 +62,9 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarRootModernization; +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -75,6 +79,8 @@ import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatu import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewModel; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.StatusBarOperatorNameViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -134,6 +140,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private StatusBarWindowControllerStore mStatusBarWindowControllerStore; + @Mock private StatusBarWindowController mStatusBarWindowController; + @Mock private StatusBarConfigurationControllerStore mStatusBarConfigurationControllerStore; + @Mock private StatusBarConfigurationController mStatusBarConfigurationController; + @Mock private DarkIconDispatcherStore mDarkIconDispatcherStore; + @Mock private DarkIconDispatcher mDarkIconDispatcher; @Rule public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @@ -145,6 +157,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Before public void setup() { + when(mStatusBarWindowControllerStore.forDisplay(anyInt())) + .thenReturn(mStatusBarWindowController); + when(mStatusBarConfigurationControllerStore.forDisplay(anyInt())) + .thenReturn(mStatusBarConfigurationController); + when(mDarkIconDispatcherStore.forDisplay(anyInt())).thenReturn(mDarkIconDispatcher); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mDependency.injectMockDependency(DarkIconDispatcher.class); @@ -1276,11 +1294,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mDumpManager, mStatusBarWindowStateController, mKeyguardUpdateMonitor, - mock(DemoModeController.class)); + mock(DemoModeController.class), + mStatusBarWindowControllerStore, + mStatusBarConfigurationControllerStore, + mDarkIconDispatcherStore); } private void setUpDaggerComponent() { - when(mStatusBarFragmentComponentFactory.create(any())) + when(mStatusBarFragmentComponentFactory.create(any(), any(), any(), any())) .thenReturn(mHomeStatusBarComponent); when(mHomeStatusBarComponent.getHeadsUpAppearanceController()) .thenReturn(mHeadsUpAppearanceController); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index b8be6aa50015..64d89c5ac8ba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -81,6 +81,14 @@ public class SysuiTestableContext extends TestableContext { return super.getDisplay(); } + @Override + public int getDisplayId() { + if (mCustomDisplay != null) { + return mCustomDisplay.getDisplayId(); + } + return super.getDisplayId(); + } + public SysuiTestableContext createDefaultDisplayContext() { Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0]; return (SysuiTestableContext) createDisplayContext(display); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt index 50a19a9bc68a..fb2e2a3b76a9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.core +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository import com.android.systemui.statusbar.window.StatusBarWindowController @@ -24,5 +26,7 @@ class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory { override fun create( statusBarWindowController: StatusBarWindowController, statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository, + statusBarConfigurationController: StatusBarConfigurationController, + darkIconDispatcher: DarkIconDispatcher, ): StatusBarInitializer = FakeStatusBarInitializer() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt index 6e990277df6b..b8dafb23b206 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.core import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.statusbar.data.repository.darkIconDispatcherStore import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() } @@ -39,6 +41,8 @@ val Kosmos.multiDisplayStatusBarInitializerStore by fakeStatusBarInitializerFactory, fakeStatusBarWindowControllerStore, fakeStatusBarModeRepository, + statusBarConfigurationControllerStore, + darkIconDispatcherStore, ) } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 09440e0e997d..600b124ffbf6 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3227,6 +3227,12 @@ public class AccountManagerService "the type and name should not be empty"); return; } + if (!type.equals(mAccountType)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "incorrect account type"); + return; + } + Account resultAccount = new Account(name, type); if (!customTokens) { saveAuthTokenToDatabase( diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 6e09a84e0f8c..e4e53f4124f3 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -1434,7 +1434,7 @@ public class ContentProviderHelper { } } } - } catch (RemoteException ignored) { + } catch (RemoteException|SecurityException ignored) { } }); } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index c82933c5069e..c99e8c8ff799 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -382,6 +382,12 @@ public class SettingsToPropertiesMapper { newSingleThreadScheduledExecutor(), (DeviceConfig.Properties properties) -> { + // send prop stage request to new storage + if (enableAconfigStorageDaemon()) { + stageFlagsInNewStorage(properties); + return; + } + for (String flagName : properties.getKeyset()) { String flagValue = properties.getString(flagName, null); if (flagName == null || flagValue == null) { @@ -409,11 +415,6 @@ public class SettingsToPropertiesMapper { setProperty(propertyName, flagValue); } - // send prop stage request to new storage - if (enableAconfigStorageDaemon()) { - stageFlagsInNewStorage(properties); - } - }); // add prop sync callback for flag local overrides @@ -423,6 +424,7 @@ public class SettingsToPropertiesMapper { (DeviceConfig.Properties properties) -> { if (enableAconfigStorageDaemon()) { setLocalOverridesInNewStorage(properties); + return; } if (Flags.supportLocalOverridesSysprops()) { diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 56cb6f49f6c4..febf24edc294 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -71,6 +71,9 @@ class InputSettingsObserver extends ContentObserver { (reason) -> updateMouseSwapPrimaryButton()), Map.entry(Settings.System.getUriFor(Settings.System.MOUSE_SCROLLING_ACCELERATION), (reason) -> updateMouseScrollingAcceleration()), + Map.entry(Settings.System.getUriFor( + Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED), + (reason) -> updateMouseAccelerationEnabled()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), (reason) -> updateTouchpadPointerSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), @@ -191,6 +194,11 @@ class InputSettingsObserver extends ContentObserver { InputSettings.isMouseScrollingAccelerationEnabled(mContext)); } + private void updateMouseAccelerationEnabled() { + mNative.setMouseAccelerationEnabled( + InputSettings.isMousePointerAccelerationEnabled(mContext)); + } + private void updateTouchpadPointerSpeed() { mNative.setTouchpadPointerSpeed( constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext))); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index ab5a680867e9..7dbde64a6412 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -138,6 +138,8 @@ interface NativeInputManagerService { void setMouseSwapPrimaryButtonEnabled(boolean enabled); + void setMouseAccelerationEnabled(boolean enabled); + void setTouchpadPointerSpeed(int speed); void setTouchpadNaturalScrollingEnabled(boolean enabled); @@ -429,6 +431,9 @@ interface NativeInputManagerService { public native void setMouseSwapPrimaryButtonEnabled(boolean enabled); @Override + public native void setMouseAccelerationEnabled(boolean enabled); + + @Override public native void setTouchpadPointerSpeed(int speed); @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 165f9d3340e3..f91b030dd1ba 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -256,7 +256,9 @@ public class ContextHubService extends IContextHubService.Stub { public void handleServiceRestart() { Log.i(TAG, "Recovering from Context Hub HAL restart..."); initExistingCallbacks(); - mHubInfoRegistry.onHalRestart(); + if (mHubInfoRegistry != null) { + mHubInfoRegistry.onHalRestart(); + } resetSettings(); if (Flags.reconnectHostEndpointsAfterHalRestart()) { mClientManager.forEachClientOfHub(mContextHubId, diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 5e6737a485af..6c0d8ad7264d 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2197,7 +2197,7 @@ class MediaRouter2ServiceImpl { mRouter.notifyRouterRegistered( getVisibleRoutes(currentRoutes), currentSystemSessionInfo); } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex); + logRemoteException("notifyRegistered", ex); } } @@ -2212,7 +2212,7 @@ class MediaRouter2ServiceImpl { try { mRouter.notifyRoutesUpdated(getVisibleRoutes(routes)); } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex); + logRemoteException("notifyRoutesUpdated", ex); } } @@ -2221,11 +2221,7 @@ class MediaRouter2ServiceImpl { mRouter.notifySessionCreated( requestId, maybeClearTransferInitiatorIdentity(sessionInfo)); } catch (RemoteException ex) { - Slog.w( - TAG, - "Failed to notify router of the session creation." - + " Router probably died.", - ex); + logRemoteException("notifySessionCreated", ex); } } @@ -2238,11 +2234,7 @@ class MediaRouter2ServiceImpl { try { mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null); } catch (RemoteException ex) { - Slog.w( - TAG, - "Failed to notify router of the session creation failure." - + " Router probably died.", - ex); + logRemoteException("notifySessionCreationFailed", ex); } } @@ -2253,10 +2245,7 @@ class MediaRouter2ServiceImpl { try { mRouter.notifySessionReleased(sessionInfo); } catch (RemoteException ex) { - Slog.w( - TAG, - "Failed to notify router of the session release. Router probably died.", - ex); + logRemoteException("notifySessionReleased", ex); } } @@ -2284,11 +2273,7 @@ class MediaRouter2ServiceImpl { } mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route); } catch (RemoteException ex) { - Slog.w( - TAG, - "getSessionHintsForCreatingSessionOnHandler: " - + "Failed to request. Router probably died.", - ex); + logRemoteException("requestCreateSessionByManager", ex); managerRecord.notifyRequestFailed( toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR); } @@ -2303,7 +2288,7 @@ class MediaRouter2ServiceImpl { try { mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo)); } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex); + logRemoteException("notifySessionInfoChanged", ex); } } @@ -2362,6 +2347,22 @@ class MediaRouter2ServiceImpl { } return false; } + + /** Logs a {@link RemoteException} occurred during the execution of {@code operation}. */ + private void logRemoteException(String operation, RemoteException exception) { + String message = + TextUtils.formatSimple( + "%s failed for %s due to %s", + operation, getDebugString(), exception.toString()); + Slog.w(TAG, message); + } + + /** Returns a human readable representation of this router record for logging purposes. */ + private String getDebugString() { + return TextUtils.formatSimple( + "Router %s (id=%d,pid=%d,userId=%d,uid=%d)", + mPackageName, mRouterId, mPid, mUserRecord.mUserId, mUid); + } } final class ManagerRecord implements IBinder.DeathRecipient { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index eeae6166873a..f50e8aa7eb7b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3082,16 +3082,42 @@ public class NotificationManagerService extends SystemService { private void sendRegisteredOnlyBroadcast(Intent baseIntent) { int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true); - Intent intent = new Intent(baseIntent).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - for (int userId : userIds) { - getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), null); - } - // explicitly send the broadcast to all DND packages, even if they aren't currently running - for (int userId : userIds) { - for (String pkg : mConditionProviders.getAllowedPackages(userId)) { - Intent pkgIntent = new Intent(baseIntent).setPackage(pkg).setFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - getContext().sendBroadcastAsUser(pkgIntent, UserHandle.of(userId)); + if (Flags.nmBinderPerfReduceZenBroadcasts()) { + for (int userId : userIds) { + Context userContext = getContext().createContextAsUser(UserHandle.of(userId), 0); + String[] dndPackages = mConditionProviders.getAllowedPackages(userId) + .toArray(new String[0]); + + // We send the broadcast to all DND packages in the second step, so leave them out + // of this first broadcast for *running* receivers. That ensures each package only + // receives it once. + Intent registeredOnlyIntent = new Intent(baseIntent) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + userContext.sendBroadcastMultiplePermissions(registeredOnlyIntent, + /* receiverPermissions= */ new String[0], + /* excludedPermissions= */ new String[0], + /* excludedPackages= */ dndPackages); + + for (String pkg : dndPackages) { + Intent pkgIntent = new Intent(baseIntent).setPackage(pkg) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + userContext.sendBroadcast(pkgIntent); + } + } + } else { + Intent intent = new Intent(baseIntent).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + for (int userId : userIds) { + getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), null); + } + + // explicitly send the broadcast to all DND packages, even if they aren't currently + // running + for (int userId : userIds) { + for (String pkg : mConditionProviders.getAllowedPackages(userId)) { + Intent pkgIntent = new Intent(baseIntent).setPackage(pkg).setFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + getContext().sendBroadcastAsUser(pkgIntent, UserHandle.of(userId)); + } } } } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 65a38ae1fcde..f15c23e110a4 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -187,3 +187,13 @@ flag { description: "Enables sound uri with vibration source in notification channel" bug: "351975435" } + +flag { + name: "nm_binder_perf_reduce_zen_broadcasts" + namespace: "systemui" + description: "Don't send duplicate zen-related (policy changed, etc) broadcasts" + bug: "324376849" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 95690cd63994..24d857b0fc1f 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -759,34 +759,29 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void handleMessage(Message msg) { BatteryCallback cb = mCallback; + if (cb == null) { + return; + } switch (msg.what) { case MSG_REPORT_CPU_UPDATE_NEEDED: - if (cb != null) { - cb.batteryNeedsCpuUpdate(); - } + cb.batteryNeedsCpuUpdate(); break; case MSG_REPORT_POWER_CHANGE: - if (cb != null) { - cb.batteryPowerChanged(msg.arg1 != 0); - } + cb.batteryPowerChanged(msg.arg1 != 0); break; case MSG_REPORT_CHARGING: - if (cb != null) { - final String action; - synchronized (BatteryStatsImpl.this) { - action = mCharging ? BatteryManager.ACTION_CHARGING - : BatteryManager.ACTION_DISCHARGING; - } - Intent intent = new Intent(action); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - cb.batterySendBroadcast(intent); + final String action; + synchronized (BatteryStatsImpl.this) { + action = mCharging ? BatteryManager.ACTION_CHARGING + : BatteryManager.ACTION_DISCHARGING; } + Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + cb.batterySendBroadcast(intent); break; case MSG_REPORT_RESET_STATS: - if (cb != null) { - cb.batteryStatsReset(); - } - } + cb.batteryStatsReset(); + } } } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 492d84f4a12f..a972ecbcac9e 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -218,11 +218,6 @@ public class AppTransitionController { if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) { tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps); tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps); - if (mDisplayContent.mAtmService.mBackNavigationController - .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) { - mDisplayContent.mAtmService.mBackNavigationController - .clearBackAnimations(false /* cancel */); - } } @TransitionOldType final int transit = getTransitCompatType( diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 819395ac60b8..37575f00363e 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -22,10 +22,8 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; -import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK; import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; @@ -50,7 +48,6 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemProperties; import android.text.TextUtils; -import android.util.ArraySet; import android.util.Pair; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -657,12 +654,6 @@ class BackNavigationController { && displayContent.isFixedRotationLaunchingApp(openActivity); } - private boolean isWaitBackTransition() { - // Ignore mWaitTransition while flag is enabled. - return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition() - || mAnimationHandler.mWaitTransition); - } - boolean isKeyguardOccluded(WindowState focusWindow) { final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController; final int displayId = focusWindow.getDisplayId(); @@ -700,65 +691,14 @@ class BackNavigationController { return false; } - // For legacy transition. - /** - * Once we find the transition targets match back animation targets, remove the target from - * list, so that transition won't count them in since the close animation was finished. - * - * @return {@code true} if the participants of this transition was animated by back gesture - * animations, and shouldn't join next transition. - */ - boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps, - ArraySet<ActivityRecord> closeApps) { - if (!isMonitoringFinishTransition()) { - return false; - } - mTmpCloseApps.addAll(closeApps); - final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps); - if (!matchAnimationTargets) { - mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps); - } - mTmpCloseApps.clear(); - return matchAnimationTargets; - } - - boolean removeIfWaitForBackTransition(ArraySet<ActivityRecord> openApps, - ArraySet<ActivityRecord> closeApps) { - if (!isWaitBackTransition()) { - return false; - } - // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from - // mOpeningApps if there is no visibility change. - if (mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) { - // remove close target from close list, open target from open list; - // but the open target can be in close list. - for (int i = openApps.size() - 1; i >= 0; --i) { - final ActivityRecord ar = openApps.valueAt(i); - if (mAnimationHandler.isTarget(ar, true /* open */)) { - openApps.removeAt(i); - } - } - for (int i = closeApps.size() - 1; i >= 0; --i) { - final ActivityRecord ar = closeApps.valueAt(i); - if (mAnimationHandler.isTarget(ar, false /* open */)) { - closeApps.removeAt(i); - } - } - return true; - } - return false; - } - void removePredictiveSurfaceIfNeeded(ActivityRecord openActivity) { mAnimationHandler.markWindowHasDrawn(openActivity); } boolean isStartingSurfaceShown(ActivityRecord openActivity) { - if (!Flags.migratePredictiveBackTransition()) { - return false; - } return mAnimationHandler.isStartingSurfaceDrawn(openActivity); } + @VisibleForTesting class NavigationMonitor { // The window which triggering the back navigation. @@ -823,24 +763,6 @@ class BackNavigationController { mObserver.sendResult(result); } - /** - * Notify an unexpected transition has happened during back navigation. - */ - private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening, - ArrayList<WindowContainer> closing) { - if (!isMonitorForRemote() && !isMonitorAnimationOrTransition()) { - return; - } - final ArrayList<WindowContainer> all = new ArrayList<>(opening); - all.addAll(closing); - for (int i = all.size() - 1; i >= 0; --i) { - if (all.get(i).hasChild(mNavigatingWindow)) { - cancelBackNavigating("transitionHappens"); - break; - } - } - } - private boolean atSameDisplay(WindowState newFocus) { if (mNavigatingWindow == null) { return false; @@ -922,8 +844,7 @@ class BackNavigationController { if (targets.isEmpty()) { return; } - final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition(); - if (migratePredictToTransition && !mAnimationHandler.mComposed) { + if (!mAnimationHandler.mComposed) { return; } else if (!isMonitoringFinishTransition()) { return; @@ -949,63 +870,40 @@ class BackNavigationController { mTmpCloseApps.add(wc); } } - final boolean matchAnimationTargets; - if (migratePredictToTransition) { - matchAnimationTargets = - mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps); - } else { - matchAnimationTargets = isWaitBackTransition() - && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK) - && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps); - } + final boolean matchAnimationTargets = mAnimationHandler + .containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b", mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets); // Don't cancel transition, let transition handler to handle it - if (!matchAnimationTargets && !migratePredictToTransition) { - mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps); - } else { - if (mAnimationHandler.mPrepareCloseTransition != null) { - Slog.e(TAG, "Gesture animation is applied on another transition?"); - return; - } - mAnimationHandler.mPrepareCloseTransition = transition; - if (!migratePredictToTransition) { - // Because the target will reparent to transition root, so it cannot be controlled - // by animation leash. Hide the close target when transition starts. - startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl()); - } - // Flag target matches and prepare to remove windowless surface. - mAnimationHandler.markStartingSurfaceMatch(startTransaction); - // release animation leash - if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) { - finishTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction); - mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null; - } + if (mAnimationHandler.mPrepareCloseTransition != null) { + Slog.e(TAG, "Gesture animation is applied on another transition?"); + return; + } + mAnimationHandler.mPrepareCloseTransition = transition; + // Flag target matches and prepare to remove windowless surface. + mAnimationHandler.markStartingSurfaceMatch(startTransaction); + // release animation leash + if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) { + finishTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction); + mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null; } mTmpOpenApps.clear(); mTmpCloseApps.clear(); } boolean isMonitorTransitionTarget(WindowContainer wc) { - if (Flags.migratePredictiveBackTransition()) { - if (!mAnimationHandler.mComposed) { - return false; - } - if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH - && wc.asActivityRecord() != null - || (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH - && wc.asTask() != null)) { - return false; - } - return (mAnimationHandler.isTarget(wc, true /* open */) - || mAnimationHandler.isTarget(wc, false /* open */)); - } else if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null) - || (mAnimationHandler.mOpenAnimAdaptor != null - && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) { - return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */); + if (!mAnimationHandler.mComposed) { + return false; } - return false; + if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH + && wc.asActivityRecord() != null + || (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH + && wc.asTask() != null)) { + return false; + } + return (mAnimationHandler.isTarget(wc, true /* open */) + || mAnimationHandler.isTarget(wc, false /* open */)); } boolean shouldPauseTouch(WindowContainer wc) { @@ -1163,12 +1061,10 @@ class BackNavigationController { WindowContainer[] open = builder.mOpenTargets; if (isActivitySwitch(close, open)) { mSwitchType = ACTIVITY_SWITCH; - if (Flags.migratePredictiveBackTransition()) { - final Pair<WindowContainer, WindowContainer[]> replaced = - promoteToTFIfNeeded(close, open); - close = replaced.first; - open = replaced.second; - } + final Pair<WindowContainer, WindowContainer[]> replaced = + promoteToTFIfNeeded(close, open); + close = replaced.first; + open = replaced.second; } else if (isTaskSwitch(close, open)) { mSwitchType = TASK_SWITCH; } else if (isDialogClose(close)) { @@ -1988,19 +1884,11 @@ class BackNavigationController { // animation was canceled return; } - if (Flags.migratePredictiveBackTransition()) { - if (mOpenAnimAdaptor == null - || mOpenAnimAdaptor.mPreparedOpenTransition == null) { - // no open nor close transition, this is window animation - if (!triggerBack) { - clearBackAnimateTarget(true /* cancel */); - } - } - } else { + if (mOpenAnimAdaptor == null + || mOpenAnimAdaptor.mPreparedOpenTransition == null) { + // no open nor close transition, this is window animation if (!triggerBack) { clearBackAnimateTarget(true /* cancel */); - } else { - mWaitTransition = true; } } } @@ -2097,8 +1985,6 @@ class BackNavigationController { } private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) { - final boolean migrateBackTransition = Flags.migratePredictiveBackTransition(); - final boolean unifyBackNavigationTransition = Flags.unifyBackNavigationTransition(); final ArrayList<ActivityRecord> affects = new ArrayList<>(); for (int i = activities.length - 1; i >= 0; --i) { final ActivityRecord activity = activities[i]; @@ -2112,23 +1998,11 @@ class BackNavigationController { } final TransitionController tc = activities[0].mTransitionController; - final Transition prepareOpen = migrateBackTransition && !unifyBackNavigationTransition + final Transition prepareOpen = !Flags.unifyBackNavigationTransition() && !tc.isCollecting() ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null; - DisplayContent commonDisplay = null; for (int i = affects.size() - 1; i >= 0; --i) { final ActivityRecord activity = affects.get(i); - if (!migrateBackTransition && !activity.isVisibleRequested()) { - // The transition could commit the visibility and in the finishing state, that could - // skip commitVisibility call in setVisibility cause the activity won't visible - // here. - // Call it again to make sure the activity could be visible while handling the - // pending animation. - // Do not performLayout during prepare animation, because it could cause focus - // window change. Let that happen after the BackNavigationInfo has returned to - // shell. - activity.commitVisibility(true, false /* performLayout */); - } activity.mTransitionController.mSnapshotController .mActivitySnapshotController.addOnBackPressedActivity(activity); activity.mLaunchTaskBehind = true; @@ -2137,16 +2011,11 @@ class BackNavigationController { "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity); activity.mTaskSupervisor.mStoppingActivities.remove(activity); - if (!migrateBackTransition) { - commonDisplay = activity.getDisplayContent(); - } else if (activity.shouldBeVisible()) { + if (activity.shouldBeVisible()) { activity.ensureActivityConfiguration(true /* ignoreVisibility */); activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */); } } - if (commonDisplay != null) { - commonDisplay.ensureActivitiesVisible(null /* starting */, true /* notifyClients */); - } if (prepareOpen != null) { if (prepareOpen.hasChanges()) { tc.requestStartTransition(prepareOpen, @@ -2172,17 +2041,10 @@ class BackNavigationController { "Setting Activity.mLauncherTaskBehind to false. Activity=%s", activity); if (cancel) { - final boolean migrateBackTransition = Flags.migratePredictiveBackTransition(); // could be visible if transition is canceled due to top activity is finishing. - if (migrateBackTransition) { - if (finishTransition && !activity.shouldBeVisible()) { - activity.commitVisibility(false /* visible */, false /* performLayout */, - true /* fromTransition */); - } - } else { - // Restore the launch-behind state - // TODO b/347168362 Change status directly during collecting for a transition. - activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token); + if (finishTransition && !activity.shouldBeVisible()) { + activity.commitVisibility(false /* visible */, false /* performLayout */, + true /* fromTransition */); } // Ignore all change activity.mTransitionController.mSnapshotController @@ -2205,9 +2067,6 @@ class BackNavigationController { /** If the open transition is playing, wait for transition to clear the animation */ private boolean canCancelAnimations() { - if (!Flags.migratePredictiveBackTransition()) { - return true; - } return mAnimationHandler.mOpenAnimAdaptor == null || mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null; } @@ -2248,17 +2107,6 @@ class BackNavigationController { mPendingAnimationBuilder = null; } } - if (result.getBoolean(BackNavigationInfo.KEY_GESTURE_FINISHED)) { - synchronized (mWindowManagerService.mGlobalLock) { - final AnimationHandler ah = mAnimationHandler; - if (!ah.mComposed || ah.mWaitTransition || ah.mOpenActivities == null - || (ah.mSwitchType != AnimationHandler.TASK_SWITCH - && ah.mSwitchType != AnimationHandler.ACTIVITY_SWITCH)) { - return; - } - setLaunchBehind(mAnimationHandler.mOpenActivities); - } - } } static TaskSnapshot getSnapshot(@NonNull WindowContainer w, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5fe1cebccd2e..145c7b37fcdc 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -7084,9 +7084,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp class RemoteInsetsControlTarget implements InsetsControlTarget { private final IDisplayWindowInsetsController mRemoteInsetsController; - private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); private final boolean mCanShowTransient; + /** The actual requested visible inset types for this display */ + private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); + + /** The component name of the top focused window on this display */ + private ComponentName mTopFocusedComponentName = null; + + /** + * The inset types that the top focused window is currently requesting to be visible. + * This may be different than the actual visible types above depending on the remote + * insets controller implementation. + */ + private @InsetsType int mTopFocusedRequestedVisibleTypes = + WindowInsets.Type.defaultVisible(); + RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) { mRemoteInsetsController = controller; mCanShowTransient = mWmService.mContext.getResources().getBoolean( @@ -7096,11 +7109,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** * Notifies the remote insets controller that the top focused window has changed. * - * @param component The application component that is open in the top focussed window. + * @param component The application component that is open in the top focused window. * @param requestedVisibleTypes The insets types requested visible by the focused window. */ void topFocusedWindowChanged(ComponentName component, @InsetsType int requestedVisibleTypes) { + if (mTopFocusedComponentName != null && mTopFocusedComponentName.equals(component) + && mTopFocusedRequestedVisibleTypes == requestedVisibleTypes) { + return; + } + mTopFocusedComponentName = component; + mTopFocusedRequestedVisibleTypes = requestedVisibleTypes; try { mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibleTypes); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index cba606cf2b0c..98ed6f76b2f9 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -101,7 +101,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // isLeashReadyForDispatching (used to dispatch the leash of the control) is // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here // again, so that the control with leash can be eventually dispatched - if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) { + if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending + && mControlTarget != null) { mGivenInsetsReady = true; ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 7276007481ab..d1585d06ae40 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -384,7 +384,7 @@ class InsetsSourceProvider { } final boolean serverVisibleChanged = mServerVisible != isServerVisible; setServerVisible(isServerVisible); - if (mControl != null) { + if (mControl != null && mControlTarget != null) { final boolean positionChanged = updateInsetsControlPosition(windowState); if (!(positionChanged || mHasPendingPosition) // The insets hint would be updated while changing the position. Here updates it diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index ce8518449230..9df65f60e8d7 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -371,7 +371,7 @@ class InsetsStateController { array.add(provider); } - void notifyControlChanged(InsetsControlTarget target, InsetsSourceProvider provider) { + void notifyControlChanged(@NonNull InsetsControlTarget target, InsetsSourceProvider provider) { addToPendingControlMaps(target, provider); notifyPendingInsetsControlChanged(); } diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 755d4c8c9fc5..6dd7d35856df 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -348,12 +348,14 @@ class PinnedTaskController { * Notifies listeners that the PIP needs to be adjusted for the IME. */ private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) { - if (mPinnedTaskListener != null) { - try { - mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight); - } catch (RemoteException e) { - Slog.e(TAG_WM, "Error delivering bounds changed event.", e); - } + if (mPinnedTaskListener == null) { + return; + } + + try { + mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Error delivering ime visibility changed event.", e); } } @@ -361,15 +363,14 @@ class PinnedTaskController { * Notifies listeners that the PIP movement bounds have changed. */ private void notifyMovementBoundsChanged(boolean fromImeAdjustment) { - synchronized (mService.mGlobalLock) { - if (mPinnedTaskListener == null) { - return; - } - try { - mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment); - } catch (RemoteException e) { - Slog.e(TAG_WM, "Error delivering actions changed event.", e); - } + if (mPinnedTaskListener == null) { + return; + } + + try { + mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Error delivering movement bounds changed event.", e); } } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 3634bc987d0a..d962b6b11187 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -845,7 +845,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { */ void positionTaskBehindHome(Task task) { final Task home = getOrCreateRootHomeTask(); - final WindowContainer homeParent = home.getParent(); + final WindowContainer homeParent = home != null ? home.getParent() : null; final Task homeParentTask = homeParent != null ? homeParent.asTask() : null; if (homeParentTask == null) { // reparent throws if parent didn't change... diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 367adc355738..a031acad638f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1339,14 +1339,24 @@ class TaskFragment extends WindowContainer<WindowContainer> { mTmpRect.set(getBounds()); for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) { final TaskFragment taskFragment = adjacentTaskFragments.get(j); - final TaskFragment adjacentTaskFragment = - taskFragment.mAdjacentTaskFragment; - if (adjacentTaskFragment == this) { + if (taskFragment.isAdjacentTo(this)) { continue; } - if (mTmpRect.intersect(taskFragment.getBounds()) - || mTmpRect.intersect(adjacentTaskFragment.getBounds())) { - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + if (Flags.allowMultipleAdjacentTaskFragments()) { + final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds()) + || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { + return mTmpRect.intersect(adjacentTf.getBounds()); + }); + if (isOccluding) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } + } else { + final TaskFragment adjacentTaskFragment = + taskFragment.mAdjacentTaskFragment; + if (mTmpRect.intersect(taskFragment.getBounds()) + || mTmpRect.intersect(adjacentTaskFragment.getBounds())) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } } } } @@ -1374,20 +1384,38 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final TaskFragment otherTaskFrag = other.asTaskFragment(); - if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) { - if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) { - if (otherTaskFrag.isTranslucent(starting) - || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) { - // Can be visible behind a translucent adjacent TaskFragments. - gotTranslucentFullscreen = true; - gotTranslucentAdjacent = true; - continue; + if (otherTaskFrag != null && otherTaskFrag.hasAdjacentTaskFragment()) { + if (Flags.allowMultipleAdjacentTaskFragments()) { + final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments( + adjacentTaskFragments::contains); + if (hasTraversedAdj) { + final boolean isTranslucent = otherTaskFrag.isTranslucent(starting) + || otherTaskFrag.forOtherAdjacentTaskFragments(adjacentTf -> { + return adjacentTf.isTranslucent(starting); + }); + if (isTranslucent) { + // Can be visible behind a translucent adjacent TaskFragments. + gotTranslucentFullscreen = true; + gotTranslucentAdjacent = true; + continue; + } + // Can not be visible behind adjacent TaskFragments. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } - // Can not be visible behind adjacent TaskFragments. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } else { - adjacentTaskFragments.add(otherTaskFrag); + if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) { + if (otherTaskFrag.isTranslucent(starting) + || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) { + // Can be visible behind a translucent adjacent TaskFragments. + gotTranslucentFullscreen = true; + gotTranslucentAdjacent = true; + continue; + } + // Can not be visible behind adjacent TaskFragments. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + } } + adjacentTaskFragments.add(otherTaskFrag); } } @@ -2725,7 +2753,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { return; } - t.setWindowCrop(mSurfaceControl, width, height); + if (fillsParent()) { + // Rely on parent's crop. + t.setCrop(mSurfaceControl, null); + } else { + t.setWindowCrop(mSurfaceControl, width, height); + } mLastSurfaceSize.set(width, height); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e45ada9438ae..5929a21e77a1 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -2661,10 +2661,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private int deleteTaskFragment(@NonNull TaskFragment taskFragment, @Nullable Transition transition) { - if (transition != null) transition.collectExistenceChange(taskFragment); + final boolean isEmpty = taskFragment.getNonFinishingActivityCount() == 0; + if (transition != null && (taskFragment.isVisibleRequested() + // In case to update existing change type. + || transition.mChanges.containsKey(taskFragment))) { + transition.collectExistenceChange(taskFragment); + } mLaunchTaskFragments.remove(taskFragment.getFragmentToken()); taskFragment.remove(true /* withTransition */, "deleteTaskFragment"); + if (isEmpty) { + // The removal of an empty TaskFragment doesn't affect lifecycle. + return 0; + } return TRANSACT_EFFECTS_LIFECYCLE; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 813fec1454a7..f634beb77329 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -345,6 +345,7 @@ public: void setMouseReverseVerticalScrollingEnabled(bool enabled); void setMouseScrollingAccelerationEnabled(bool enabled); void setMouseSwapPrimaryButtonEnabled(bool enabled); + void setMouseAccelerationEnabled(bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -502,6 +503,9 @@ private: // True if the mouse primary button is swapped (left/right buttons). bool mouseSwapPrimaryButtonEnabled{false}; + // True if the mouse cursor will accelerate as the mouse moves faster. + bool mousePointerAccelerationEnabled{true}; + // The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest). int32_t touchpadPointerSpeed{0}; @@ -847,6 +851,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->mouseReverseVerticalScrollingEnabled = mLocked.mouseReverseVerticalScrollingEnabled; outConfig->mouseSwapPrimaryButtonEnabled = mLocked.mouseSwapPrimaryButtonEnabled; + outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled; outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed; outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled; @@ -1458,6 +1463,21 @@ void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) { InputReaderConfiguration::Change::MOUSE_SETTINGS); } +void NativeInputManager::setMouseAccelerationEnabled(bool enabled) { + { // acquire lock + std::scoped_lock _l(mLock); + + if (mLocked.mousePointerAccelerationEnabled == enabled) { + return; + } + + mLocked.mousePointerAccelerationEnabled = enabled; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::POINTER_SPEED); +} + void NativeInputManager::setPointerSpeed(int32_t speed) { { // acquire lock std::scoped_lock _l(mLock); @@ -3220,6 +3240,11 @@ static void nativeSetMouseSwapPrimaryButtonEnabled(JNIEnv* env, jobject nativeIm im->setMouseSwapPrimaryButtonEnabled(enabled); } +static void nativeSetMouseAccelerationEnabled(JNIEnv* env, jobject nativeImplObj, bool enabled) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setMouseAccelerationEnabled(enabled); +} + static jboolean nativeSetKernelWakeEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -3280,6 +3305,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setMouseScrollingAccelerationEnabled", "(Z)V", (void*)nativeSetMouseScrollingAccelerationEnabled}, {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled}, + {"setMouseAccelerationEnabled", "(Z)V", (void*)nativeSetMouseAccelerationEnabled}, {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed}, {"setTouchpadNaturalScrollingEnabled", "(Z)V", (void*)nativeSetTouchpadNaturalScrollingEnabled}, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index bcda2c0662ca..301165f8151d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -644,6 +644,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { doNothing().when(mContext).sendBroadcast(any(), anyString()); doNothing().when(mContext).sendBroadcastAsUser(any(), any()); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); + doNothing().when(mContext).sendBroadcastMultiplePermissions(any(), any(), any(), any()); + doReturn(mContext).when(mContext).createContextAsUser(eq(mUser), anyInt()); + TestableContentResolver cr = mock(TestableContentResolver.class); when(mContext.getContentResolver()).thenReturn(cr); doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt()); @@ -11235,7 +11238,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void onZenModeChanged_sendsBroadcasts() throws Exception { + @DisableFlags(Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS) + public void onZenModeChanged_sendsBroadcasts_oldBehavior() throws Exception { when(mAmi.getCurrentUserId()).thenReturn(100); when(mUmInternal.getProfileIds(eq(100), anyBoolean())).thenReturn(new int[]{100, 101, 102}); when(mConditionProviders.getAllowedPackages(anyInt())).then(new Answer<List<String>>() { @@ -11288,6 +11292,74 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS) + public void onZenModeChanged_sendsBroadcasts() throws Exception { + when(mAmi.getCurrentUserId()).thenReturn(100); + when(mUmInternal.getProfileIds(eq(100), anyBoolean())).thenReturn(new int[]{100, 101, 102}); + when(mConditionProviders.getAllowedPackages(anyInt())).then(new Answer<List<String>>() { + @Override + public List<String> answer(InvocationOnMock invocation) { + int userId = invocation.getArgument(0); + switch (userId) { + case 100: + return Lists.newArrayList("a", "b", "c"); + case 101: + return Lists.newArrayList(); + case 102: + return Lists.newArrayList("b"); + default: + throw new IllegalArgumentException( + "Why would you ask for packages of userId " + userId + "?"); + } + } + }); + Context context100 = mock(Context.class); + doReturn(context100).when(mContext).createContextAsUser(eq(UserHandle.of(100)), anyInt()); + Context context101 = mock(Context.class); + doReturn(context101).when(mContext).createContextAsUser(eq(UserHandle.of(101)), anyInt()); + Context context102 = mock(Context.class); + doReturn(context102).when(mContext).createContextAsUser(eq(UserHandle.of(102)), anyInt()); + + mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null, + "testing!", false); + waitForIdle(); + + // Verify broadcasts per user: registered receivers first, then DND packages. + InOrder inOrder = inOrder(context100, context101, context102); + + inOrder.verify(context100).sendBroadcastMultiplePermissions( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)), + eq(new String[0]), eq(new String[0]), eq(new String[] {"a", "b", "c"})); + inOrder.verify(context100).sendBroadcast( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setPackage("a") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT))); + inOrder.verify(context100).sendBroadcast( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setPackage("b") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT))); + inOrder.verify(context100).sendBroadcast( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setPackage("c") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT))); + + inOrder.verify(context101).sendBroadcastMultiplePermissions( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)), + eq(new String[0]), eq(new String[0]), eq(new String[] {})); + + inOrder.verify(context102).sendBroadcastMultiplePermissions( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)), + eq(new String[0]), eq(new String[0]), eq(new String[] {"b"})); + inOrder.verify(context102).sendBroadcast( + eqIntent(new Intent(ACTION_INTERRUPTION_FILTER_CHANGED) + .setPackage("b") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT))); + } + + @Test @EnableFlags(android.app.Flags.FLAG_MODES_API) public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception { mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged( diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index e0e8aa45231b..fbb123e25b29 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -17,8 +17,6 @@ package com.android.server.wm; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -60,8 +58,6 @@ import android.os.Looper; import android.os.RemoteCallback; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.util.ArraySet; import android.view.WindowManager; import android.window.BackAnimationAdapter; import android.window.BackMotionEvent; @@ -76,7 +72,6 @@ import android.window.TaskSnapshot; import android.window.WindowOnBackInvokedDispatcher; import com.android.server.LocalServices; -import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -625,43 +620,6 @@ public class BackNavigationControllerTests extends WindowTestsBase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION) - public void testTransitionHappensCancelNavigation() { - // Create a floating task and a fullscreen task, then navigating on fullscreen task. - // The navigation should not been cancelled when transition happens on floating task, and - // only be cancelled when transition happens on the navigating task. - final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD); - final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask); - - final Task fullscreenTask = createTopTaskWithActivity(); - withSystemCallback(fullscreenTask); - final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity(); - - final CountDownLatch navigationObserver = new CountDownLatch(1); - startBackNavigation(navigationObserver); - - final ArraySet<ActivityRecord> opening = new ArraySet<>(); - final ArraySet<ActivityRecord> closing = new ArraySet<>(); - final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask); - opening.add(secondFloatingActivity); - closing.add(baseFloatingActivity); - mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); - assertEquals("Transition happen on an irrelevant task, callback should not been called", - 1, navigationObserver.getCount()); - - // Create a new activity above navigation target, the transition should cancel navigation. - final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask); - opening.clear(); - closing.clear(); - opening.add(topFullscreenActivity); - closing.add(baseFullscreenActivity); - mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); - assertEquals("Transition happen on navigation task, callback should have been called", - 0, navigationObserver.getCount()); - } - - @Test public void testWindowFocusChangeCancelNavigation() { Task task = createTopTaskWithActivity(); withSystemCallback(task); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 19c1ce2616af..ef58498b351c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -638,6 +638,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() { doReturn(true).when(mTaskFragment).isAttached(); + doReturn(1).when(mTaskFragment).getNonFinishingActivityCount(); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); // Throw exception if the transaction is trying to change a window that is not organized by diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index c0f251e06d17..dc4adcc4315b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -132,8 +132,8 @@ public class TaskFragmentTest extends WindowTestsBase { final int parentSw = parentConfig.smallestScreenWidthDp; final Rect bounds = new Rect(parentBounds); bounds.inset(100, 100); - mTaskFragment.setBounds(bounds); mTaskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + mTaskFragment.setBounds(bounds); // Calculate its own sw with smaller bounds in multi-window mode. assertNotEquals(parentSw, mTaskFragment.getConfiguration().smallestScreenWidthDp); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index e7c9e927b311..e27dbe588183 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1869,10 +1869,18 @@ public class UsageStatsService extends SystemService implements } private boolean shouldDeleteObsoleteData(UserHandle userHandle) { - final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); - // If a profile owner is not defined for the given user, obsolete data should be deleted - return dpmInternal == null - || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null; + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + final SupervisionManagerInternal smInternal = getSupervisionManagerInternal(); + // If supervision is not enabled for the given user, obsolete data should be deleted. + return smInternal == null + || !smInternal.isSupervisionEnabledForUser(userHandle.getIdentifier()); + } else { + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); + // If a profile owner is not defined for the given user, obsolete data should be deleted + return dpmInternal == null + || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) + == null; + } } private String buildFullToken(String packageName, String token) { diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp index 2ebede39504e..87ea5db57db4 100644 --- a/tools/systemfeatures/Android.bp +++ b/tools/systemfeatures/Android.bp @@ -100,3 +100,72 @@ sh_test_host { unit_test: true, }, } + +java_library_host { + name: "systemfeatures-errorprone-lib", + srcs: [ + ":systemfeatures-gen-metadata-srcs", + "errorprone/java/**/*.java", + ], + static_libs: [ + "//external/error_prone:error_prone_core", + "guava", + "jsr305", + ], + libs: [ + "//external/auto:auto_service_annotations", + ], + javacflags: [ + // These exports are needed because this errorprone plugin access some private classes + // of the java compiler. + "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], + plugins: [ + "//external/auto:auto_service_plugin", + ], +} + +java_plugin { + name: "systemfeatures-errorprone", + static_libs: ["systemfeatures-errorprone-lib"], +} + +java_test_host { + name: "systemfeatures-errorprone-tests", + srcs: [ + "errorprone/tests/java/**/*.java", + ], + java_resource_dirs: ["tests/src"], + java_resources: [ + ":systemfeatures-errorprone-tests-data", + ], + static_libs: [ + "compile-testing-prebuilt", + "error_prone_test_helpers", + "framework-annotations-lib", + "hamcrest", + "hamcrest-library", + "junit", + "systemfeatures-errorprone-lib", + "truth", + ], + test_options: { + unit_test: true, + }, +} + +java_system_features_srcs { + name: "systemfeatures-gen-metadata-srcs", + full_class_name: "com.android.systemfeatures.RoSystemFeaturesMetadata", + metadata_only: true, + visibility: ["//visibility:private"], +} + +filegroup { + name: "systemfeatures-errorprone-tests-data", + path: "tests/src", + srcs: ["tests/src/android/**/*.java"], + visibility: ["//visibility:private"], +} diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md index 5836f81e5fd3..b1fec1a34723 100644 --- a/tools/systemfeatures/README.md +++ b/tools/systemfeatures/README.md @@ -4,8 +4,110 @@ System features exposed from `PackageManager` are defined and aggregated as `<feature>` xml attributes across various partitions, and are currently queried -at runtime through the framework. This directory contains tooling that will -support *build-time* queries of select system features, enabling optimizations +at runtime through the framework. This directory contains tooling that supports +*build-time* queries of select system features, enabling optimizations like code stripping and conditionally dependencies when so configured. -### TODO(b/203143243): Expand readme after landing codegen. +### System Feature Codegen + +As not all system features can be fully specified or defined at build time (e.g. +updatable partitisions and apex modules can change/remove such features), we +use a conditional, build flag approach that allows a given device to customize +the subset of build-time defined system features that are immutable and cannot +be updated. + +#### Build Flags + +System features that can be fixed at build-time are declared in a common +location, `build/release/flag_declarations/`. These have the form +`RELEASE_SYSTEM_FEATURE_${X}`, where `${X}` corresponds to a feature defined in +`PackageManager`, e.g., `TELEVISION` or `WATCH`. + +Build flag values can then be defined per device (or form factor), where such +values either indicate the existence/version of the system feature, or that the +feature is unavailable, e.g., for TV, we could define these build flag values: +``` +name: "RELEASE_SYSTEM_FEATURE_TELEVISION" +value: { + string_value: "0" # Feature version = 0 +} +``` +``` +name: "RELEASE_SYSTEM_FEATURE_WATCH" +value: { + string_value: "UNAVAILABLE" +} +``` + +See also [SystemFeaturesGenerator](src/com/android/systemfeatures/SystemFeaturesGenerator.kt) +for more details. + +#### Runtime Queries + +Each declared build flag system feature is routed into codegen, generating a +getter API in the internal class, `com.android.internal.pm.RoSystemFeatures`: +``` +class RoSystemFeatures { + ... + public static boolean hasFeatureX(Context context); + ... +} +``` +By default, these queries simply fall back to the usual +`PackageManager.hasSystemFeature(...)` runtime queries. However, if a device +defines these features via build flags, the generated code will add annotations +indicating fixed value for this query, and adjust the generated code to return +the value directly. This in turn enables build-time stripping and optimization. + +> **_NOTE:_** Any build-time defined system features will also be implicitly +used to accelerate calls to `PackageManager.hasSystemFeature(...)` for the +feature, avoiding binder calls when possible. + +#### Lint + +A new `ErrorProne` rule is introduced to assist with migration and maintenance +of codegen APIs for build-time defined system features. This is defined in the +`systemfeatures-errorprone` build rule, which can be added to any Java target's +`plugins` list. + +// TODO(b/203143243): Add plugin to key system targets after initial migration. + +1) Add the plugin dependency to a given `${TARGET}`: +``` +java_library { + name: "${TARGET}", + plugins: ["systemfeatures-errorprone"], +} +``` +2) Run locally: +``` +RUN_ERROR_PRONE=true m ${TARGET} +``` +3) (Optional) Update the target rule to generate in-place patch files: +``` +java_library { + name: "${TARGET}", + plugins: ["systemfeatures-errorprone"], + // DO NOT SUBMIT: GENERATE IN-PLACE PATCH FILES + errorprone: { + javacflags: [ + "-XepPatchChecks:RoSystemFeaturesChecker", + "-XepPatchLocation:IN_PLACE", + ], + } + ... +} +``` +``` +RUN_ERROR_PRONE=true m ${TARGET} +``` + +See also [RoSystemFeaturesChecker](errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java) +for more details. + +> **_NOTE:_** Not all system feature queries or targets need or should be +migrated. Only system features that are explicitly declared with build flags, +and only targets that are built with the platform (i.e., not updatable), are +candidates for this linting and migration, e.g., SystemUI, System Server, etc... + +// TODO(b/203143243): Wrap the in-place lint updates with a simple script for convenience. diff --git a/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java new file mode 100644 index 000000000000..78123774205a --- /dev/null +++ b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java @@ -0,0 +1,119 @@ +/* + * 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.systemfeatures.errorprone; + +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; + +import com.android.systemfeatures.RoSystemFeaturesMetadata; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.tools.javac.code.Symbol; + +@AutoService(BugChecker.class) +@BugPattern( + name = "RoSystemFeaturesChecker", + summary = "Use RoSystemFeature instead of PackageManager.hasSystemFeature", + explanation = + "Directly invoking `PackageManager.hasSystemFeature` is less efficient than using" + + " the `RoSystemFeatures` helper class. This check flags invocations like" + + " `context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FOO)`" + + " and suggests replacing them with" + + " `com.android.internal.pm.RoSystemFeatures.hasFeatureFoo(context)`.", + severity = WARNING) +public class RoSystemFeaturesChecker extends BugChecker + implements BugChecker.MethodInvocationTreeMatcher { + + private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager"; + private static final String CONTEXT_CLASS = "android.content.Context"; + private static final String RO_SYSTEM_FEATURE_SIMPLE_CLASS = "RoSystemFeatures"; + private static final String RO_SYSTEM_FEATURE_CLASS = + "com.android.internal.pm." + RO_SYSTEM_FEATURE_SIMPLE_CLASS; + private static final String GET_PACKAGE_MANAGER_METHOD = "getPackageManager"; + private static final String HAS_SYSTEM_FEATURE_METHOD = "hasSystemFeature"; + private static final String FEATURE_PREFIX = "FEATURE_"; + + private static final Matcher<ExpressionTree> HAS_SYSTEM_FEATURE_MATCHER = + Matchers.instanceMethod() + .onDescendantOf(PACKAGE_MANAGER_CLASS) + .named(HAS_SYSTEM_FEATURE_METHOD) + .withParameters(String.class.getName()); + + private static final Matcher<ExpressionTree> GET_PACKAGE_MANAGER_MATCHER = + Matchers.instanceMethod() + .onDescendantOf(CONTEXT_CLASS) + .named(GET_PACKAGE_MANAGER_METHOD); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (!HAS_SYSTEM_FEATURE_MATCHER.matches(tree, state)) { + return Description.NO_MATCH; + } + + // Check if the PackageManager was obtained from a Context instance. + ExpressionTree packageManager = ASTHelpers.getReceiver(tree); + if (!GET_PACKAGE_MANAGER_MATCHER.matches(packageManager, state)) { + return Description.NO_MATCH; + } + + // Get the feature argument and check if it's a PackageManager.FEATURE_X constant. + ExpressionTree feature = tree.getArguments().isEmpty() ? null : tree.getArguments().get(0); + Symbol featureSymbol = ASTHelpers.getSymbol(feature); + if (featureSymbol == null + || !featureSymbol.isStatic() + || !featureSymbol.getSimpleName().toString().startsWith(FEATURE_PREFIX) + || ASTHelpers.enclosingClass(featureSymbol) == null + || !ASTHelpers.enclosingClass(featureSymbol) + .getQualifiedName() + .contentEquals(PACKAGE_MANAGER_CLASS)) { + return Description.NO_MATCH; + } + + // Check if the feature argument is part of the RoSystemFeatures API surface. + String featureName = featureSymbol.getSimpleName().toString(); + String methodName = RoSystemFeaturesMetadata.getMethodNameForFeatureName(featureName); + if (methodName == null) { + return Description.NO_MATCH; + } + + // Generate the appropriate fix. + String replacement = + String.format( + "%s.%s(%s)", + RO_SYSTEM_FEATURE_SIMPLE_CLASS, + methodName, + state.getSourceForNode(ASTHelpers.getReceiver(packageManager))); + // Note that ErrorProne doesn't offer a seamless way of removing the `PackageManager` import + // if unused after fix application, so for now we only offer best effort import suggestions. + SuggestedFix fix = + SuggestedFix.builder() + .replace(tree, replacement) + .addImport(RO_SYSTEM_FEATURE_CLASS) + .removeStaticImport(PACKAGE_MANAGER_CLASS + "." + featureName) + .build(); + return describeMatch(tree, fix); + } +} diff --git a/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java new file mode 100644 index 000000000000..c517b2495ee4 --- /dev/null +++ b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 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.systemfeatures.errorprone; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.CompilationTestHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RoSystemFeaturesCheckerTest { + private BugCheckerRefactoringTestHelper mRefactoringHelper; + private CompilationTestHelper mCompilationHelper; + + @Before + public void setUp() { + mCompilationHelper = + CompilationTestHelper.newInstance(RoSystemFeaturesChecker.class, getClass()); + mRefactoringHelper = + BugCheckerRefactoringTestHelper.newInstance( + RoSystemFeaturesChecker.class, getClass()); + } + + @Test + public void testNoDiagnostic() { + mCompilationHelper + .addSourceFile("/android/content/Context.java") + .addSourceFile("/android/content/pm/PackageManager.java") + .addSourceLines("Example.java", + """ + import android.content.Context; + import android.content.pm.PackageManager; + public class Example { + void test(Context context) { + boolean hasCustomFeature = context.getPackageManager() + .hasSystemFeature("my.custom.feature"); + boolean hasNonAnnotatedFeature = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_NOT_ANNOTATED); + boolean hasNonRoApiFeature = context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_NOT_IN_RO_FEATURE_API); + } + } + """) + .doTest(); + } + + @Test + public void testDiagnostic() { + mCompilationHelper + .addSourceFile("/android/content/Context.java") + .addSourceFile("/android/content/pm/PackageManager.java") + .addSourceLines("Example.java", + """ + import android.content.Context; + import android.content.pm.PackageManager; + public class Example { + void test(Context context) { + boolean hasFeature = context.getPackageManager() + // BUG: Diagnostic contains: + .hasSystemFeature(PackageManager.FEATURE_PC); + } + } + """) + .doTest(); + } + + @Test + public void testFix() { + mRefactoringHelper + .addInputLines("Example.java", + """ + import static android.content.pm.PackageManager.FEATURE_WATCH; + + import android.content.Context; + import android.content.pm.PackageManager; + public class Example { + static class CustomContext extends Context {}; + private CustomContext mContext; + void test(Context context) { + boolean hasPc = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_PC); + boolean hasWatch = context.getPackageManager() + .hasSystemFeature(FEATURE_WATCH); + } + } + """) + .addOutputLines("Example.java", + """ + import android.content.Context; + import android.content.pm.PackageManager; + import com.android.internal.pm.RoSystemFeatures; + public class Example { + static class CustomContext extends Context {}; + private CustomContext mContext; + void test(Context context) { + boolean hasPc = RoSystemFeatures.hasFeaturePc(mContext); + boolean hasWatch = RoSystemFeatures.hasFeatureWatch(context); + } + } + """) + // Don't try compiling the output, as it requires pulling in the full set of code + // dependencies. + .allowBreakingChanges() + .doTest(); + } +} diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt index f260e2733843..ea660b013893 100644 --- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -53,11 +53,20 @@ import javax.lang.model.element.Modifier * public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures(); * } * </pre> + * + * <p> If `--metadata-only=true` is set, the resulting class would simply be: + * <pre> + * package com.foo; + * public final class RoSystemFeatures { + * public static String getMethodNameForFeatureName(String featureName); + * } + * </pre> */ object SystemFeaturesGenerator { private const val FEATURE_ARG = "--feature=" private const val FEATURE_APIS_ARG = "--feature-apis=" private const val READONLY_ARG = "--readonly=" + private const val METADATA_ONLY_ARG = "--metadata-only=" private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") private val CONTEXT_CLASS = ClassName.get("android.content", "Context") private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo") @@ -84,6 +93,8 @@ object SystemFeaturesGenerator { println(" runtime passthrough API will be generated, regardless") println(" of the `--readonly` flag. This allows decoupling the") println(" API surface from variations in device feature sets.") + println(" --metadata-only=true|false Whether to simply output metadata about the") + println(" generated API surface.") } /** Main entrypoint for build-time system feature codegen. */ @@ -106,6 +117,7 @@ object SystemFeaturesGenerator { } var readonly = false + var metadataOnly = false var outputClassName: ClassName? = null val featureArgs = mutableListOf<FeatureInfo>() // We could just as easily hardcode this list, as the static API surface should change @@ -115,6 +127,8 @@ object SystemFeaturesGenerator { when { arg.startsWith(READONLY_ARG) -> readonly = arg.substring(READONLY_ARG.length).toBoolean() + arg.startsWith(METADATA_ONLY_ARG) -> + metadataOnly = arg.substring(METADATA_ONLY_ARG.length).toBoolean() arg.startsWith(FEATURE_ARG) -> { featureArgs.add(parseFeatureArg(arg)) } @@ -155,9 +169,13 @@ object SystemFeaturesGenerator { .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addJavadoc("@hide") - addFeatureMethodsToClass(classBuilder, features.values) - addMaybeFeatureMethodToClass(classBuilder, features.values) - addGetFeaturesMethodToClass(classBuilder, features.values) + if (metadataOnly) { + addMetadataMethodToClass(classBuilder, features.values) + } else { + addFeatureMethodsToClass(classBuilder, features.values) + addMaybeFeatureMethodToClass(classBuilder, features.values) + addGetFeaturesMethodToClass(classBuilder, features.values) + } // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency. JavaFile.builder(outputClassName.packageName(), classBuilder.build()) @@ -214,11 +232,8 @@ object SystemFeaturesGenerator { features: Collection<FeatureInfo>, ) { for (feature in features) { - // Turn "FEATURE_FOO" into "hasFeatureFoo". - val methodName = - "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, feature.name) val methodBuilder = - MethodSpec.methodBuilder(methodName) + MethodSpec.methodBuilder(feature.methodName) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addJavadoc("Check for ${feature.name}.\n\n@hide") .returns(Boolean::class.java) @@ -341,5 +356,32 @@ object SystemFeaturesGenerator { builder.addMethod(methodBuilder.build()) } - private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) + /* + * Adds a metadata helper method that maps FEATURE_FOO names to their generated hasFeatureFoo() + * API counterpart, if defined. + */ + private fun addMetadataMethodToClass( + builder: TypeSpec.Builder, + features: Collection<FeatureInfo>, + ) { + val methodBuilder = + MethodSpec.methodBuilder("getMethodNameForFeatureName") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addJavadoc("@return \"hasFeatureFoo\" if FEATURE_FOO is in the API, else null") + .returns(String::class.java) + .addParameter(String::class.java, "featureVarName") + + methodBuilder.beginControlFlow("switch (featureVarName)") + for (feature in features) { + methodBuilder.addStatement("case \$S: return \$S", feature.name, feature.methodName) + } + methodBuilder.addStatement("default: return null").endControlFlow() + + builder.addMethod(methodBuilder.build()) + } + + private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) { + // Turn "FEATURE_FOO" into "hasFeatureFoo". + val methodName get() = "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name) + } } diff --git a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java index 74ce6daaffc4..560454b65b7e 100644 --- a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java +++ b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java @@ -36,8 +36,8 @@ public class SystemFeaturesMetadataProcessorTest { @Test public void testSdkFeatureCount() { // See the fake PackageManager definition in this directory. - // It defines 5 annotated features, and any/all other constants should be ignored. - assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5); + // It defines 6 annotated features, and any/all other constants should be ignored. + assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(6); } @Test diff --git a/tools/systemfeatures/tests/src/Context.java b/tools/systemfeatures/tests/src/android/content/Context.java index 630bc0771a01..630bc0771a01 100644 --- a/tools/systemfeatures/tests/src/Context.java +++ b/tools/systemfeatures/tests/src/android/content/Context.java diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java index 9d57edc64ca5..9d57edc64ca5 100644 --- a/tools/systemfeatures/tests/src/FeatureInfo.java +++ b/tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java diff --git a/tools/systemfeatures/tests/src/PackageManager.java b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java index 839a9377476d..4a9edd61b55b 100644 --- a/tools/systemfeatures/tests/src/PackageManager.java +++ b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java @@ -36,6 +36,9 @@ public class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_WIFI = "wifi"; + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NOT_IN_RO_FEATURE_API = "not_in_ro_feature_api"; + @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String FEATURE_INTENT_CATEGORY = "intent_category_with_feature_name_prefix"; @@ -47,4 +50,9 @@ public class PackageManager { public boolean hasSystemFeature(String featureName, int version) { return false; } + + /** @hide */ + public boolean hasSystemFeature(String featureName) { + return hasSystemFeature(featureName, 0); + } } diff --git a/tools/systemfeatures/tests/src/ArrayMap.java b/tools/systemfeatures/tests/src/android/util/ArrayMap.java index a5ed9b088896..a5ed9b088896 100644 --- a/tools/systemfeatures/tests/src/ArrayMap.java +++ b/tools/systemfeatures/tests/src/android/util/ArrayMap.java diff --git a/tools/systemfeatures/tests/src/ArraySet.java b/tools/systemfeatures/tests/src/android/util/ArraySet.java index 0eb8f298bd89..0eb8f298bd89 100644 --- a/tools/systemfeatures/tests/src/ArraySet.java +++ b/tools/systemfeatures/tests/src/android/util/ArraySet.java |