Merge "Don't override bounds to include insets for floating activities" into main
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index e092499..65bc8cc 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -44,6 +44,7 @@
"apct-perftests-resources-manager-apps",
"apct-perftests-utils",
"collector-device-lib",
+ "conscrypt-test-support",
"compatibility-device-util-axt",
"junit",
"junit-params",
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/BufferType.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/BufferType.java
new file mode 100644
index 0000000..bdc2a82
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/BufferType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import java.nio.ByteBuffer;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Enumeration that provides allocation of direct or heap buffers.
+ */
+@SuppressWarnings("unused")
+public enum BufferType {
+ HEAP {
+ @Override
+ ByteBuffer newBuffer(int size) {
+ return ByteBuffer.allocate(size);
+ }
+ },
+ DIRECT {
+ @Override
+ ByteBuffer newBuffer(int size) {
+ return ByteBuffer.allocateDirect(size);
+ }
+ };
+
+ abstract ByteBuffer newBuffer(int size);
+
+ ByteBuffer newApplicationBuffer(SSLEngine engine) {
+ return newBuffer(engine.getSession().getApplicationBufferSize());
+ }
+
+ ByteBuffer newPacketBuffer(SSLEngine engine) {
+ return newBuffer(engine.getSession().getPacketBufferSize());
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
new file mode 100644
index 0000000..c69ae39
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 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.conscrypt;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import org.conscrypt.TestUtils;
+
+import java.nio.ByteBuffer;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Benchmark for comparing cipher encrypt performance.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class CipherEncryptPerfTest {
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ public enum BufferType {
+ ARRAY,
+ HEAP_HEAP,
+ HEAP_DIRECT,
+ DIRECT_DIRECT,
+ DIRECT_HEAP
+ }
+
+ private enum MyCipherFactory implements CipherFactory {
+ JDK {
+ @Override
+ public Cipher newCipher(String transformation)
+ throws NoSuchPaddingException, NoSuchAlgorithmException {
+ return Cipher.getInstance(transformation);
+ }
+ },
+ CONSCRYPT {
+ @Override
+ public Cipher newCipher(String transformation)
+ throws NoSuchPaddingException, NoSuchAlgorithmException {
+ return Cipher.getInstance(transformation, TestUtils.getConscryptProvider());
+ }
+ };
+ }
+
+ private class Config {
+ BufferType b_bufferType;
+ CipherFactory c_provider;
+ Transformation a_tx;
+ Config(BufferType bufferType, CipherFactory cipherFactory, Transformation transformation) {
+ b_bufferType = bufferType;
+ c_provider = cipherFactory;
+ a_tx = transformation;
+ }
+ public BufferType bufferType() {
+ return b_bufferType;
+ }
+
+ public CipherFactory cipherFactory() {
+ return c_provider;
+ }
+
+ public Transformation transformation() {
+ return a_tx;
+ }
+ }
+
+ private Object[] getParams() {
+ return new Object[][] {
+ new Object[] {new Config(BufferType.ARRAY,
+ MyCipherFactory.CONSCRYPT,
+ Transformation.AES_CBC_PKCS5)},
+ new Object[] {new Config(BufferType.ARRAY,
+ MyCipherFactory.CONSCRYPT,
+ Transformation.AES_ECB_PKCS5)},
+ new Object[] {new Config(BufferType.ARRAY,
+ MyCipherFactory.CONSCRYPT,
+ Transformation.AES_GCM_NO)},
+ new Object[] {new Config(BufferType.ARRAY,
+ MyCipherFactory.CONSCRYPT,
+ Transformation.AES_GCM_SIV)},
+ };
+ }
+
+ private EncryptStrategy encryptStrategy;
+
+ @Test
+ @Parameters(method = "getParams")
+ public void encrypt(Config config) throws Exception {
+ switch (config.bufferType()) {
+ case ARRAY:
+ encryptStrategy = new ArrayStrategy(config);
+ break;
+ default:
+ encryptStrategy = new ByteBufferStrategy(config);
+ break;
+ }
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ encryptStrategy.encrypt();
+ }
+ }
+
+ private static abstract class EncryptStrategy {
+ private final Key key;
+ final Cipher cipher;
+ final int outputSize;
+
+ EncryptStrategy(Config config) throws Exception {
+ Transformation tx = config.transformation();
+ key = tx.newEncryptKey();
+ cipher = config.cipherFactory().newCipher(tx.toFormattedString());
+ initCipher();
+
+ int messageSize = messageSize(tx.toFormattedString());
+ outputSize = cipher.getOutputSize(messageSize);
+ }
+
+ final void initCipher() throws Exception {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ }
+
+ final int messageSize(String transformation) throws Exception {
+ Cipher conscryptCipher = Cipher.getInstance(
+ transformation, TestUtils.getConscryptProvider());
+ conscryptCipher.init(Cipher.ENCRYPT_MODE, key);
+ return conscryptCipher.getBlockSize() > 0 ?
+ conscryptCipher.getBlockSize() : 128;
+ }
+
+ final byte[] newMessage() {
+ return TestUtils.newTextMessage(cipher.getBlockSize());
+ }
+
+ abstract int encrypt() throws Exception;
+ }
+
+ private static final class ArrayStrategy extends EncryptStrategy {
+ private final byte[] plainBytes;
+ private final byte[] cipherBytes;
+
+ ArrayStrategy(Config config) throws Exception {
+ super(config);
+
+ plainBytes = newMessage();
+ cipherBytes = new byte[outputSize];
+ }
+
+ @Override
+ int encrypt() throws Exception {
+ initCipher();
+ return cipher.doFinal(plainBytes, 0, plainBytes.length, cipherBytes, 0);
+ }
+ }
+
+ private static final class ByteBufferStrategy extends EncryptStrategy {
+ private final ByteBuffer input;
+ private final ByteBuffer output;
+
+ ByteBufferStrategy(Config config) throws Exception {
+ super(config);
+
+ switch (config.bufferType()) {
+ case HEAP_HEAP:
+ input = ByteBuffer.wrap(newMessage());
+ output = ByteBuffer.allocate(outputSize);
+ break;
+ case HEAP_DIRECT:
+ input = ByteBuffer.wrap(newMessage());
+ output = ByteBuffer.allocateDirect(outputSize);
+ break;
+ case DIRECT_DIRECT:
+ input = toDirect(newMessage());
+ output = ByteBuffer.allocateDirect(outputSize);
+ break;
+ case DIRECT_HEAP:
+ input = toDirect(newMessage());
+ output = ByteBuffer.allocate(outputSize);
+ break;
+ default: {
+ throw new IllegalStateException(
+ "Unexpected buffertype: " + config.bufferType());
+ }
+ }
+ }
+
+ @Override
+ int encrypt() throws Exception {
+ initCipher();
+ input.position(0);
+ output.clear();
+ return cipher.doFinal(input, output);
+ }
+
+ private static ByteBuffer toDirect(byte[] data) {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
+ buffer.put(data);
+ buffer.flip();
+ return buffer;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherFactory.java
new file mode 100644
index 0000000..f8a3d5f
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+
+/**
+ * Factory for {@link Cipher} instances.
+ */
+public interface CipherFactory {
+ Cipher newCipher(String transformation) throws NoSuchPaddingException, NoSuchAlgorithmException;
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java
new file mode 100644
index 0000000..1a7258a
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.nio.channels.ClosedChannelException;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.conscrypt.ChannelType;
+
+/**
+ * Client-side endpoint. Provides basic services for sending/receiving messages from the client
+ * socket.
+ */
+final class ClientEndpoint {
+ private final SSLSocket socket;
+ private InputStream input;
+ private OutputStream output;
+
+ ClientEndpoint(SSLSocketFactory socketFactory, ChannelType channelType, int port,
+ String[] protocols, String[] ciphers) throws IOException {
+ socket = channelType.newClientSocket(socketFactory, InetAddress.getLoopbackAddress(), port);
+ socket.setEnabledProtocols(protocols);
+ socket.setEnabledCipherSuites(ciphers);
+ }
+
+ void start() {
+ try {
+ socket.startHandshake();
+ input = socket.getInputStream();
+ output = socket.getOutputStream();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ void stop() {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ int readMessage(byte[] buffer) {
+ try {
+ int totalBytesRead = 0;
+ while (totalBytesRead < buffer.length) {
+ int remaining = buffer.length - totalBytesRead;
+ int bytesRead = input.read(buffer, totalBytesRead, remaining);
+ if (bytesRead == -1) {
+ break;
+ }
+ totalBytesRead += bytesRead;
+ }
+ return totalBytesRead;
+ } catch (SSLException e) {
+ if (e.getCause() instanceof EOFException) {
+ return -1;
+ }
+ throw new RuntimeException(e);
+ } catch (ClosedChannelException e) {
+ // Thrown for channel-based sockets. Just treat like EOF.
+ return -1;
+ } catch (SocketException e) {
+ // The socket was broken. Just treat like EOF.
+ return -1;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void sendMessage(byte[] data) {
+ try {
+ output.write(data);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void flush() {
+ try {
+ output.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
new file mode 100644
index 0000000..dd9f4eb
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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.conscrypt;
+
+import org.conscrypt.ChannelType;
+import org.conscrypt.TestUtils;
+import static org.conscrypt.TestUtils.getCommonProtocolSuites;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.junit.Assert.assertEquals;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketException;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+
+import org.junit.Rule;
+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.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class ClientSocketPerfTest {
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Provider for the test configuration
+ */
+ private class Config {
+ EndpointFactory a_clientFactory;
+ EndpointFactory b_serverFactory;
+ int c_messageSize;
+ 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;
+ }
+ public EndpointFactory clientFactory() {
+ return a_clientFactory;
+ }
+
+ public EndpointFactory serverFactory() {
+ return b_serverFactory;
+ }
+
+ public int messageSize() {
+ return c_messageSize;
+ }
+
+ public String cipher() {
+ return d_cipher;
+ }
+
+ public ChannelType channelType() {
+ return e_channelType;
+ }
+
+ public PerfTestProtocol protocol() {
+ return f_protocol;
+ }
+ }
+
+ private Object[] getParams() {
+ return new Object[][] {
+ new Object[] {new Config(
+ EndpointFactory.CONSCRYPT,
+ EndpointFactory.CONSCRYPT,
+ 64,
+ "AES128-GCM",
+ ChannelType.CHANNEL,
+ PerfTestProtocol.TLSv13)},
+ };
+ }
+
+
+ private ClientEndpoint client;
+ private ServerEndpoint server;
+ private byte[] message;
+ private ExecutorService executor;
+ private Future<?> sendingFuture;
+ private volatile boolean stopping;
+
+ private static final AtomicLong bytesCounter = new AtomicLong();
+ private AtomicBoolean recording = new AtomicBoolean();
+
+ private void setup(Config config) throws Exception {
+ message = newTextMessage(512);
+
+ // Always use the same server for consistency across the benchmarks.
+ server = config.serverFactory().newServer(
+ ChannelType.CHANNEL, 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();
+
+ client = config.clientFactory().newClient(
+ config.channelType(), server.port(), config.protocol().getProtocols(), ciphers(config));
+ 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();
+ }
+ }
+ });
+ }
+
+ void close() throws Exception {
+ stopping = true;
+
+ // Wait for the sending thread to stop.
+ sendingFuture.get(5, TimeUnit.SECONDS);
+
+ client.stop();
+ server.stop();
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 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);
+ }
+ recording.set(false);
+ close();
+ }
+
+ void reset() {
+ stopping = false;
+ bytesCounter.set(0);
+ }
+
+ 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/EndpointFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java
new file mode 100644
index 0000000..0655f45
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EndpointFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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.conscrypt;
+
+import org.conscrypt.ChannelType;
+import org.conscrypt.TestUtils;
+import java.io.IOException;
+import java.security.Provider;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Utility for creating test client and server instances.
+ */
+public enum EndpointFactory {
+ CONSCRYPT(newConscryptFactories(false)),
+ CONSCRYPT_ENGINE(newConscryptFactories(true));
+
+ private final Factories factories;
+
+ EndpointFactory(Factories factories) {
+ this.factories = factories;
+ }
+
+ public ClientEndpoint newClient(ChannelType channelType, int port, String[] protocols,
+ String[] ciphers) throws IOException {
+ return new ClientEndpoint(
+ factories.clientFactory, channelType, port, protocols, ciphers);
+ }
+
+ public ServerEndpoint newServer(ChannelType channelType, int messageSize,
+ String[] protocols, String[] ciphers) throws IOException {
+ return new ServerEndpoint(factories.serverFactory, factories.serverSocketFactory,
+ channelType, messageSize, protocols, ciphers);
+ }
+
+ private static final class Factories {
+ final SSLSocketFactory clientFactory;
+ final SSLSocketFactory serverFactory;
+ final SSLServerSocketFactory serverSocketFactory;
+
+ private Factories(SSLSocketFactory clientFactory, SSLSocketFactory serverFactory,
+ SSLServerSocketFactory serverSocketFactory) {
+ this.clientFactory = clientFactory;
+ this.serverFactory = serverFactory;
+ this.serverSocketFactory = serverSocketFactory;
+ }
+ }
+
+ private static Factories newConscryptFactories(boolean useEngineSocket) {
+ Provider provider = TestUtils.getConscryptProvider();
+ SSLContext clientContext = TestUtils.newClientSslContext(provider);
+ SSLContext serverContext = TestUtils.newServerSslContext(provider);
+ final SSLSocketFactory clientFactory = clientContext.getSocketFactory();
+ final SSLSocketFactory serverFactory = serverContext.getSocketFactory();
+ final SSLServerSocketFactory serverSocketFactory = serverContext.getServerSocketFactory();
+ TestUtils.setUseEngineSocket(clientFactory, useEngineSocket);
+ TestUtils.setUseEngineSocket(serverFactory, useEngineSocket);
+ TestUtils.setUseEngineSocket(serverSocketFactory, useEngineSocket);
+ return new Factories(clientFactory, serverFactory, serverSocketFactory);
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/OWNERS b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/OWNERS
new file mode 100644
index 0000000..7efabfd
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 24949
+include platform/libcore:/OWNERS
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/PerfTestProtocol.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/PerfTestProtocol.java
new file mode 100644
index 0000000..4defe71
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/PerfTestProtocol.java
@@ -0,0 +1,33 @@
+/*
+ * 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.conscrypt;
+
+public enum PerfTestProtocol {
+
+ TLSv13("TLSv1.3"),
+ TLSv12("TLSv1.2");
+
+ private final String[] protocols;
+
+ PerfTestProtocol(String... protocols) {
+ this.protocols = protocols;
+ }
+
+ public String[] getProtocols() {
+ return protocols.clone();
+ }
+}
\ 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
new file mode 100644
index 0000000..3631c3f
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.SocketException;
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.conscrypt.ChannelType;
+
+/**
+ * A simple socket-based test server.
+ */
+final class ServerEndpoint {
+ /**
+ * A processor for receipt of a single message.
+ */
+ public interface MessageProcessor {
+ void processMessage(byte[] message, int numBytes, OutputStream os);
+ }
+
+ /**
+ * A {@link MessageProcessor} that simply echos back the received message to the client.
+ */
+ public static final class EchoProcessor implements MessageProcessor {
+ @Override
+ public void processMessage(byte[] message, int numBytes, OutputStream os) {
+ try {
+ os.write(message, 0, numBytes);
+ os.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private final ServerSocket serverSocket;
+ private final ChannelType channelType;
+ private final SSLSocketFactory socketFactory;
+ private final int messageSize;
+ private final String[] protocols;
+ private final String[] cipherSuites;
+ private final byte[] buffer;
+ private SSLSocket socket;
+ private ExecutorService executor;
+ private InputStream inputStream;
+ private OutputStream outputStream;
+ private volatile boolean stopping;
+ private volatile MessageProcessor messageProcessor = new EchoProcessor();
+ private volatile Future<?> processFuture;
+
+ ServerEndpoint(SSLSocketFactory socketFactory, SSLServerSocketFactory serverSocketFactory,
+ ChannelType channelType, int messageSize, String[] protocols,
+ String[] cipherSuites) throws IOException {
+ this.serverSocket = channelType.newServerSocket(serverSocketFactory);
+ this.socketFactory = socketFactory;
+ this.channelType = channelType;
+ this.messageSize = messageSize;
+ this.protocols = protocols;
+ this.cipherSuites = cipherSuites;
+ buffer = new byte[messageSize];
+ }
+
+ void setMessageProcessor(MessageProcessor messageProcessor) {
+ this.messageProcessor = messageProcessor;
+ }
+
+ Future<?> start() throws IOException {
+ executor = Executors.newSingleThreadExecutor();
+ return executor.submit(new AcceptTask());
+ }
+
+ void stop() {
+ try {
+ stopping = true;
+
+ if (socket != null) {
+ socket.close();
+ socket = null;
+ }
+
+ if (processFuture != null) {
+ processFuture.get(5, TimeUnit.SECONDS);
+ }
+
+ serverSocket.close();
+
+ if (executor != null) {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ executor = null;
+ }
+ } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int port() {
+ return serverSocket.getLocalPort();
+ }
+
+ private final class AcceptTask implements Runnable {
+ @Override
+ public void run() {
+ try {
+ if (stopping) {
+ return;
+ }
+ socket = channelType.accept(serverSocket, socketFactory);
+ socket.setEnabledProtocols(protocols);
+ socket.setEnabledCipherSuites(cipherSuites);
+
+ socket.startHandshake();
+
+ inputStream = socket.getInputStream();
+ outputStream = socket.getOutputStream();
+
+ if (stopping) {
+ return;
+ }
+ processFuture = executor.submit(new ProcessTask());
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private final class ProcessTask implements Runnable {
+ @Override
+ public void run() {
+ try {
+ Thread thread = Thread.currentThread();
+ while (!stopping && !thread.isInterrupted()) {
+ int bytesRead = readMessage();
+ if (!stopping && !thread.isInterrupted()) {
+ messageProcessor.processMessage(buffer, bytesRead, outputStream);
+ }
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private int readMessage() throws IOException {
+ int totalBytesRead = 0;
+ while (!stopping && totalBytesRead < messageSize) {
+ try {
+ int remaining = messageSize - totalBytesRead;
+ int bytesRead = inputStream.read(buffer, totalBytesRead, remaining);
+ if (bytesRead == -1) {
+ break;
+ }
+ totalBytesRead += bytesRead;
+ } catch (SSLException e) {
+ if (e.getCause() instanceof EOFException) {
+ break;
+ }
+ throw e;
+ } catch (ClosedChannelException e) {
+ // Thrown for channel-based sockets. Just treat like EOF.
+ break;
+ } catch (SocketException e) {
+ // The socket was broken. Just treat like EOF.
+ break;
+ }
+ }
+ return totalBytesRead;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
new file mode 100644
index 0000000..ba2a65a
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import org.conscrypt.ChannelType;
+import static org.conscrypt.TestUtils.getCommonProtocolSuites;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import android.conscrypt.ServerEndpoint.MessageProcessor;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Benchmark for comparing performance of server socket implementations.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class ServerSocketPerfTest {
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Provider for the benchmark configuration
+ */
+ private class Config {
+ EndpointFactory a_clientFactory;
+ EndpointFactory b_serverFactory;
+ int c_messageSize;
+ String d_cipher;
+ ChannelType e_channelType;
+ Config(EndpointFactory clientFactory,
+ EndpointFactory serverFactory,
+ int messageSize,
+ String cipher,
+ ChannelType channelType) {
+ a_clientFactory = clientFactory;
+ b_serverFactory = serverFactory;
+ c_messageSize = messageSize;
+ d_cipher = cipher;
+ e_channelType = channelType;
+ }
+ public EndpointFactory clientFactory() {
+ return a_clientFactory;
+ }
+
+ public EndpointFactory serverFactory() {
+ return b_serverFactory;
+ }
+
+ public int messageSize() {
+ return c_messageSize;
+ }
+
+ public String cipher() {
+ return d_cipher;
+ }
+
+ public ChannelType channelType() {
+ return e_channelType;
+ }
+ }
+
+ private Object[] getParams() {
+ return new Object[][] {
+ new Object[] {new Config(
+ EndpointFactory.CONSCRYPT,
+ EndpointFactory.CONSCRYPT,
+ 64,
+ "AES128-GCM",
+ ChannelType.CHANNEL)},
+ };
+ }
+
+ private ClientEndpoint client;
+ private ServerEndpoint server;
+ private ExecutorService executor;
+ private Future<?> receivingFuture;
+ private volatile boolean stopping;
+ private static final AtomicLong bytesCounter = new AtomicLong();
+ private AtomicBoolean recording = new AtomicBoolean();
+
+ private void setup(final Config config) throws Exception {
+ recording.set(false);
+
+ byte[] message = newTextMessage(config.messageSize());
+
+ final ChannelType channelType = config.channelType();
+
+ server = config.serverFactory().newServer(
+ channelType, config.messageSize(), getCommonProtocolSuites(), ciphers(config));
+ server.setMessageProcessor(new MessageProcessor() {
+ @Override
+ public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
+ try {
+ try {
+ while (!stopping) {
+ os.write(inMessage, 0, numBytes);
+ }
+ } finally {
+ os.flush();
+ }
+ } catch (SocketException e) {
+ // Just ignore.
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+
+ Future<?> connectedFuture = server.start();
+
+ // Always use the same client for consistency across the benchmarks.
+ client = config.clientFactory().newClient(
+ ChannelType.CHANNEL, server.port(), getCommonProtocolSuites(), ciphers(config));
+ 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();
+
+ executor = Executors.newSingleThreadExecutor();
+ receivingFuture = executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ Thread thread = Thread.currentThread();
+ byte[] buffer = new byte[config.messageSize()];
+ while (!stopping && !thread.isInterrupted()) {
+ int numBytes = client.readMessage(buffer);
+ if (numBytes < 0) {
+ return;
+ }
+ assertEquals(config.messageSize(), numBytes);
+
+ // Increment the message counter if we're recording.
+ if (recording.get()) {
+ bytesCounter.addAndGet(numBytes);
+ }
+ }
+ }
+ });
+ }
+
+ 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);
+ }
+
+ @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);
+ }
+ close();
+ }
+
+ 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/Transformation.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
new file mode 100644
index 0000000..78fe732
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 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.conscrypt;
+
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.KeyGenerator;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Supported cipher transformations.
+ */
+@SuppressWarnings({"ImmutableEnumChecker", "unused"})
+public enum Transformation {
+ AES_CBC_PKCS5("AES", "CBC", "PKCS5Padding", new AesKeyGen()),
+ AES_ECB_PKCS5("AES", "ECB", "PKCS5Padding", new AesKeyGen()),
+ AES_GCM_NO("AES", "GCM", "NoPadding", new AesKeyGen()),
+ AES_GCM_SIV("AES", "GCM_SIV", "NoPadding", new AesKeyGen()),
+ RSA_ECB_PKCS1("RSA", "ECB", "PKCS1Padding", new RsaKeyGen());
+
+ Transformation(String algorithm, String mode, String padding, KeyGen keyGen) {
+ this.algorithm = algorithm;
+ this.mode = mode;
+ this.padding = padding;
+ this.keyGen = keyGen;
+ }
+
+ final String algorithm;
+ final String mode;
+ final String padding;
+ final KeyGen keyGen;
+
+ String toFormattedString() {
+ return algorithm + "/" + mode + "/" + padding;
+ }
+
+ Key newEncryptKey() {
+ return keyGen.newEncryptKey();
+ }
+
+ private interface KeyGen { Key newEncryptKey(); }
+
+ private static final class RsaKeyGen implements KeyGen {
+ @Override
+ public Key newEncryptKey() {
+ try {
+ // Use Bouncy castle
+ KeyPairGenerator generator =
+ KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
+ generator.initialize(2048);
+ KeyPair pair = generator.generateKeyPair();
+ return pair.getPublic();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static final class AesKeyGen implements KeyGen {
+ @Override
+ public Key newEncryptKey() {
+ try {
+ // Just use the JDK's provider.
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(256);
+ return keyGen.generateKey();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index d80d3c8..b955032 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -18,11 +18,8 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
-
import android.app.Activity;
import android.content.Context;
-import android.os.Bundle;
import android.os.RemoteException;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -131,7 +128,6 @@
final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
- final Bundle mOutBundle = windowSessionRelayoutInfo() ? null : new Bundle();
final WindowRelayoutResult mOutRelayoutResult;
final IWindow mWindow;
final View mView;
@@ -152,26 +148,16 @@
mHeight = mView.getMeasuredHeight();
mOutSurfaceControl = mView.getViewRootImpl().getSurfaceControl();
mViewVisibility = visibilitySupplier;
- mOutRelayoutResult = windowSessionRelayoutInfo()
- ? new WindowRelayoutResult(mOutFrames, mOutMergedConfiguration,
- mOutSurfaceControl, mOutInsetsState, mOutControls)
- : null;
+ mOutRelayoutResult = new WindowRelayoutResult(mOutFrames, mOutMergedConfiguration,
+ mOutSurfaceControl, mOutInsetsState, mOutControls);
}
void runBenchmark(BenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
mRelayoutSeq++;
- if (windowSessionRelayoutInfo()) {
- session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
- mOutRelayoutResult);
- } else {
- session.relayoutLegacy(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
- mOutFrames, mOutMergedConfiguration, mOutSurfaceControl,
- mOutInsetsState, mOutControls, mOutBundle);
- }
+ session.relayout(mWindow, mParams, mWidth, mHeight, mViewVisibility.getAsInt(),
+ mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */, mOutRelayoutResult);
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index b982d12..dfa7206 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4925,7 +4925,6 @@
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
sdFilter.addAction(Intent.ACTION_USER_STOPPED);
if (mStartUserBeforeScheduledAlarms) {
- sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
sdFilter.addAction(Intent.ACTION_USER_REMOVED);
}
sdFilter.addAction(Intent.ACTION_UID_REMOVED);
@@ -4958,14 +4957,6 @@
mTemporaryQuotaReserve.removeForUser(userHandle);
}
return;
- case Intent.ACTION_LOCKED_BOOT_COMPLETED:
- final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (handle >= 0) {
- if (mStartUserBeforeScheduledAlarms) {
- mUserWakeupStore.onUserStarted(handle);
- }
- }
- return;
case Intent.ACTION_USER_REMOVED:
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (user >= 0) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
index 7fc630c..6840877 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -98,12 +98,7 @@
*/
@GuardedBy("mUserWakeupLock")
private final SparseLongArray mUserStarts = new SparseLongArray();
- /**
- * A list of users that are in a phase after they have been started but before alarms were
- * initialized.
- */
- @GuardedBy("mUserWakeupLock")
- private final SparseLongArray mStartingUsers = new SparseLongArray();
+
private Executor mBackgroundExecutor;
private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(),
ROOT_DIR_NAME);
@@ -124,9 +119,6 @@
*/
public void addUserWakeup(int userId, long alarmTime) {
synchronized (mUserWakeupLock) {
- // This should not be needed, but if an app in the user is scheduling an alarm clock, we
- // consider the user start complete. There is a dedicated removal when user is started.
- mStartingUsers.delete(userId);
mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
}
updateUserListFile();
@@ -192,23 +184,10 @@
}
/**
- * Move user from wakeup list to starting user list.
+ * Remove scheduled user wakeup from the list when it is started.
*/
public void onUserStarting(int userId) {
- synchronized (mUserWakeupLock) {
- final long wakeup = getWakeupTimeForUser(userId);
- if (wakeup >= 0) {
- mStartingUsers.put(userId, wakeup);
- mUserStarts.delete(userId);
- }
- }
- }
-
- /**
- * Remove userId from starting user list once start is complete.
- */
- public void onUserStarted(int userId) {
- if (deleteWakeupFromStartingUsers(userId)) {
+ if (deleteWakeupFromUserStarts(userId)) {
updateUserListFile();
}
}
@@ -217,7 +196,7 @@
* Remove userId from the store when the user is removed.
*/
public void onUserRemoved(int userId) {
- if (deleteWakeupFromUserStarts(userId) || deleteWakeupFromStartingUsers(userId)) {
+ if (deleteWakeupFromUserStarts(userId)) {
updateUserListFile();
}
}
@@ -238,21 +217,6 @@
}
/**
- * Remove wakeup for a given userId from mStartingUsers.
- * @return true if an entry is found and the list of wakeups changes.
- */
- private boolean deleteWakeupFromStartingUsers(int userId) {
- int index;
- synchronized (mUserWakeupLock) {
- index = mStartingUsers.indexOfKey(userId);
- if (index >= 0) {
- mStartingUsers.removeAt(index);
- }
- }
- return index >= 0;
- }
-
- /**
* Get the soonest wakeup time in the store.
*/
public long getNextWakeupTime() {
@@ -299,9 +263,6 @@
for (int i = 0; i < mUserStarts.size(); i++) {
listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i)));
}
- for (int i = 0; i < mStartingUsers.size(); i++) {
- listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i)));
- }
}
Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second));
for (int i = 0; i < listOfUsers.size(); i++) {
@@ -329,7 +290,6 @@
}
synchronized (mUserWakeupLock) {
mUserStarts.clear();
- mStartingUsers.clear();
}
try (FileInputStream fis = userWakeupFile.openRead()) {
final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
@@ -396,14 +356,6 @@
TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw);
pw.println();
}
- pw.println(mStartingUsers.size() + " starting users: ");
- for (int i = 0; i < mStartingUsers.size(); i++) {
- pw.print("UserId: ");
- pw.print(mStartingUsers.keyAt(i));
- pw.print(", userStartTime: ");
- TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw);
- pw.println();
- }
pw.decreaseIndent();
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 5eeb299..6e42fe3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3453,6 +3453,8 @@
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
+ field @FlaggedApi("android.view.accessibility.global_action_media_play_pause") public static final int GLOBAL_ACTION_MEDIA_PLAY_PAUSE = 22; // 0x16
+ field @FlaggedApi("android.view.accessibility.global_action_menu") public static final int GLOBAL_ACTION_MENU = 21; // 0x15
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -6699,7 +6701,7 @@
method @NonNull public android.app.Notification.Builder setExtras(android.os.Bundle);
method @NonNull public android.app.Notification.Builder setFlag(int, boolean);
method @NonNull public android.app.Notification.Builder setForegroundServiceBehavior(int);
- method @NonNull public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT) public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
method @NonNull public android.app.Notification.Builder setGroup(String);
method @NonNull public android.app.Notification.Builder setGroupAlertBehavior(int);
method @NonNull public android.app.Notification.Builder setGroupSummary(boolean);
@@ -9824,6 +9826,7 @@
method public void onAssociationPending(@NonNull android.content.IntentSender);
method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
method public abstract void onFailure(@Nullable CharSequence);
+ method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
}
public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index fd9600c..19ffc17f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -67,6 +67,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.Flags;
import android.view.inputmethod.EditorInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -625,6 +626,18 @@
*/
public static final int GLOBAL_ACTION_DPAD_CENTER = 20;
+ /**
+ * Action to trigger menu key event.
+ */
+ @FlaggedApi(Flags.FLAG_GLOBAL_ACTION_MENU)
+ public static final int GLOBAL_ACTION_MENU = 21;
+
+ /**
+ * Action to trigger media play/pause key event.
+ */
+ @FlaggedApi(Flags.FLAG_GLOBAL_ACTION_MEDIA_PLAY_PAUSE)
+ public static final int GLOBAL_ACTION_MEDIA_PLAY_PAUSE = 22;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3b9a5d3..baed4fd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2303,19 +2303,26 @@
&& PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
VirtualDeviceManager virtualDeviceManager =
getSystemService(VirtualDeviceManager.class);
- VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
- if (virtualDevice != null) {
- if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
- && !virtualDevice.hasCustomAudioInputSupport())
- || (Objects.equals(permission, Manifest.permission.CAMERA)
- && !virtualDevice.hasCustomCameraSupport())) {
- deviceId = Context.DEVICE_ID_DEFAULT;
- }
- } else {
+ if (virtualDeviceManager == null) {
Slog.e(
TAG,
- "virtualDevice is not found when device id is not default. deviceId = "
+ "VDM is not enabled when device id is not default. deviceId = "
+ deviceId);
+ } else {
+ VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+ if (virtualDevice != null) {
+ if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+ && !virtualDevice.hasCustomAudioInputSupport())
+ || (Objects.equals(permission, Manifest.permission.CAMERA)
+ && !virtualDevice.hasCustomCameraSupport())) {
+ deviceId = Context.DEVICE_ID_DEFAULT;
+ }
+ } else {
+ Slog.e(
+ TAG,
+ "virtualDevice is not found when device id is not default. deviceId = "
+ + deviceId);
+ }
}
}
@@ -3169,6 +3176,11 @@
public void updateDeviceId(int updatedDeviceId) {
if (updatedDeviceId != Context.DEVICE_ID_DEFAULT) {
VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+ if (vdm == null) {
+ throw new IllegalArgumentException(
+ "VDM is not enabled when updating to non-default device id: "
+ + updatedDeviceId);
+ }
if (!vdm.isValidVirtualDeviceId(updatedDeviceId)) {
throw new IllegalArgumentException(
"Not a valid ID of the default device or any virtual device: "
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ffb920b..15b13dc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -757,15 +757,6 @@
void addStartInfoTimestamp(int key, long timestampNs, int userId);
/**
- * Reports view related timestamps to be added to the calling apps most
- * recent {@link ApplicationStartInfo}.
- *
- * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start
- * @param framePresentedTimeNs Clock monotonic time in nanoseconds of frame presented
- */
- oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs);
-
- /**
* Return a list of {@link ApplicationExitInfo} records.
*
* <p class="note"> Note: System stores these historical information in a ring buffer, older
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a1c4267..aea15e1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5058,6 +5058,7 @@
* @see Notification#fullScreenIntent
*/
@NonNull
+ @RequiresPermission(android.Manifest.permission.USE_FULL_SCREEN_INTENT)
public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
mN.fullScreenIntent = intent;
setFlag(FLAG_HIGH_PRIORITY, highPriority);
@@ -6050,6 +6051,7 @@
bindProfileBadge(contentView, p);
bindAlertedIcon(contentView, p);
bindExpandButton(contentView, p);
+ bindCloseButton(contentView, p);
mN.mUsesStandardHeader = true;
}
@@ -6071,6 +6073,15 @@
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
}
+ private void bindCloseButton(RemoteViews contentView, StandardTemplateParams p) {
+ // set default colors
+ int bgColor = getBackgroundColor(p);
+ int backgroundColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor);
+ int foregroundColor = Colors.flattenAlpha(getPrimaryTextColor(p), backgroundColor);
+ contentView.setInt(R.id.close_button, "setForegroundColor", foregroundColor);
+ contentView.setInt(R.id.close_button, "setBackgroundColor", backgroundColor);
+ }
+
private void bindHeaderChronometerAndTime(RemoteViews contentView,
StandardTemplateParams p, boolean hasTextToLeft) {
if (!p.mHideTime && showsTimeOrChronometer()) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b4ad1c8..34cfa58 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,6 +21,8 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import static java.util.Collections.unmodifiableMap;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -56,6 +58,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
+import android.util.ArrayMap;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -75,6 +78,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -119,29 +123,31 @@
* is created successfully.
*/
public static final int RESULT_OK = -1;
-
+ //TODO(b/331459560) Need to update the java doc after API cut for W.
/**
* The result code to propagate back to the user activity, indicates if the association dialog
* is implicitly cancelled.
* E.g. phone is locked, switch to another app or press outside the dialog.
*/
public static final int RESULT_CANCELED = 0;
-
+ //TODO(b/331459560) Need to update the java doc after API cut for W.
/**
* The result code to propagate back to the user activity, indicates the association dialog
* is explicitly declined by the users.
*/
public static final int RESULT_USER_REJECTED = 1;
-
+ //TODO(b/331459560) Need to update the java doc after API cut for W.
/**
* The result code to propagate back to the user activity, indicates the association
* dialog is dismissed if there's no device found after 20 seconds.
*/
public static final int RESULT_DISCOVERY_TIMEOUT = 2;
-
+ //TODO(b/331459560) Need to update the java doc after API cut for W.
/**
* The result code to propagate back to the user activity, indicates the internal error
* in CompanionDeviceManager.
+ * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
+ * {@link AssociationInfo}.
*/
public static final int RESULT_INTERNAL_ERROR = 3;
@@ -368,12 +374,22 @@
*/
public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
+ //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
/**
* Invoked if the association could not be created.
*
* @param error error message.
*/
public abstract void onFailure(@Nullable CharSequence error);
+
+ /**
+ * Invoked if the association could not be created.
+ *
+ * @param resultCode indicate the particular reason why the association
+ * could not be created.
+ */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+ public void onFailure(@ResultCode int resultCode) {}
}
private final ICompanionDeviceManager mService;
@@ -1803,8 +1819,12 @@
}
@Override
- public void onFailure(CharSequence error) throws RemoteException {
- execute(mCallback::onFailure, error);
+ public void onFailure(@ResultCode int resultCode) {
+ if (Flags.associationFailureCode()) {
+ execute(mCallback::onFailure, resultCode);
+ }
+
+ execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
}
private <T> void execute(Consumer<T> callback, T arg) {
@@ -1988,4 +2008,15 @@
}
}
}
+
+ private static final Map<Integer, String> RESULT_CODE_TO_REASON;
+ static {
+ final Map<Integer, String> map = new ArrayMap<>();
+ map.put(RESULT_CANCELED, REASON_CANCELED);
+ map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+ map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+ map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
+
+ RESULT_CODE_TO_REASON = unmodifiableMap(map);
+ }
}
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index 8cc2a71..b1be30a 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
oneway void onAssociationCreated(in AssociationInfo associationInfo);
- oneway void onFailure(in CharSequence error);
+ oneway void onFailure(in int resultCode);
}
\ No newline at end of file
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index fd4ba83..ee9114f 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -53,4 +53,12 @@
namespace: "companion"
description: "Unpair with an associated bluetooth device"
bug: "322237619"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "association_failure_code"
+ is_exported: true
+ namespace: "companion"
+ description: "Enable association failure code API"
+ bug: "331459560"
+}
diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java
index fb9536f..3acb373 100644
--- a/core/java/android/content/ComponentCallbacks.java
+++ b/core/java/android/content/ComponentCallbacks.java
@@ -58,7 +58,9 @@
* @deprecated Since API level 14 this is superseded by
* {@link ComponentCallbacks2#onTrimMemory}.
* Since API level 34 this is never called.
- * Apps targeting API level 34 and above may provide an empty implementation.
+ * If you're overriding ComponentCallbacks2#onTrimMemory and
+ * your minSdkVersion is greater than API 14, you can provide
+ * an empty implementation for this method.
*/
@Deprecated
void onLowMemory();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4fcf6b6..24fd000 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4451,7 +4451,8 @@
* @see #DISPLAY_HASH_SERVICE
* @see android.view.displayhash.DisplayHashManager
*/
- public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
+ // TODO(b/347269120): Re-add @Nullable
+ public abstract Object getSystemService(@ServiceName @NonNull String name);
/**
* Return the handle to a system-level service by class.
@@ -4495,7 +4496,8 @@
* <b>never</b> throw a {@link RuntimeException} if the name is not supported.
*/
@SuppressWarnings("unchecked")
- public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) {
+ // TODO(b/347269120): Re-add @Nullable
+ public final <T> T getSystemService(@NonNull Class<T> serviceClass) {
// Because subclasses may override getSystemService(String) we cannot
// perform a lookup by class alone. We must first map the class to its
// service name then invoke the string-based method.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e0cf0a5..a475c29 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -932,7 +932,8 @@
}
@Override
- public @Nullable Object getSystemService(String name) {
+ // TODO(b/347269120): Re-add @Nullable
+ public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 32d2a6f..c2424e8 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -347,7 +347,9 @@
VirtualDeviceManager virtualDeviceManager =
context.getSystemService(VirtualDeviceManager.class);
- return virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
+ return virtualDeviceManager == null
+ ? DEVICE_POLICY_DEFAULT
+ : virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
}
/**
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 708f8a1..9eb9745 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -581,7 +581,9 @@
if (mVirtualDeviceManager == null) {
mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
}
- return mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
+ return mVirtualDeviceManager == null
+ ? DEVICE_POLICY_DEFAULT
+ : mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
}
// TODO(b/147726300): Investigate how to support foldables/multi-display devices.
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index f9a2dbb..b1b4de3 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -63,48 +63,53 @@
}
void close() {
+ ProgramList programList;
synchronized (mLock) {
- if (mProgramList != null) {
- mProgramList.close();
+ if (mProgramList == null) {
+ return;
}
+ programList = mProgramList;
}
+ programList.close();
}
void setProgramListObserver(@Nullable ProgramList programList,
ProgramList.OnCloseListener closeListener) {
Objects.requireNonNull(closeListener, "CloseListener cannot be null");
+ ProgramList prevProgramList;
synchronized (mLock) {
- if (mProgramList != null) {
- Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
- mProgramList.close();
- }
+ prevProgramList = mProgramList;
mProgramList = programList;
- if (programList == null) {
- return;
- }
- programList.setOnCloseListener(() -> {
- synchronized (mLock) {
- if (mProgramList != programList) {
- return;
- }
- mProgramList = null;
- mLastCompleteList = null;
- }
- closeListener.onClose();
- });
- programList.addOnCompleteListener(() -> {
- synchronized (mLock) {
- if (mProgramList != programList) {
- return;
- }
- mLastCompleteList = programList.toList();
- if (mDelayedCompleteCallback) {
- Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
- sendBackgroundScanCompleteLocked();
- }
- }
- });
}
+ if (prevProgramList != null) {
+ Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
+ prevProgramList.close();
+ }
+ if (programList == null) {
+ return;
+ }
+ programList.setOnCloseListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) {
+ return;
+ }
+ mProgramList = null;
+ mLastCompleteList = null;
+ }
+ closeListener.onClose();
+ });
+ programList.addOnCompleteListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) {
+ return;
+ }
+ mLastCompleteList = programList.toList();
+ if (mDelayedCompleteCallback) {
+ Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
+ sendBackgroundScanCompleteLocked();
+ }
+ }
+ });
}
@Nullable List<RadioManager.ProgramInfo> getLastCompleteList() {
@@ -245,12 +250,14 @@
@Override
public void onProgramListUpdated(ProgramList.Chunk chunk) {
mHandler.post(() -> {
+ ProgramList programList;
synchronized (mLock) {
if (mProgramList == null) {
return;
}
- mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null"));
+ programList = mProgramList;
}
+ programList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null"));
});
}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index eda755c..b01ffe5 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -31,3 +31,14 @@
description: "Enables the adaptive haptics feature"
bug: "305961689"
}
+
+flag {
+ namespace: "haptics"
+ name: "cancel_by_appops"
+ description: "Cancels ongoing vibrations when the appops mode changes to disallow them"
+ bug: "230745615"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 141ffc9..a698b18 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -374,20 +374,22 @@
public @NonNull List<PermissionGroupUsage> getOpUsageDataForAllDevices(
boolean includeMicrophoneUsage) {
List<PermissionGroupUsage> allUsages = new ArrayList<>();
- List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices();
- ArraySet<String> persistentDeviceIds = new ArraySet<>();
- for (int num = 0; num < virtualDevices.size(); num++) {
- persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId());
+ if (mVirtualDeviceManager != null) {
+ List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices();
+ ArraySet<String> persistentDeviceIds = new ArraySet<>();
+
+ for (int num = 0; num < virtualDevices.size(); num++) {
+ persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId());
+ }
+ persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+
+ for (int index = 0; index < persistentDeviceIds.size(); index++) {
+ allUsages.addAll(
+ getOpUsageDataByDevice(includeMicrophoneUsage,
+ persistentDeviceIds.valueAt(index)));
+ }
}
- persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
-
- for (int index = 0; index < persistentDeviceIds.size(); index++) {
- allUsages.addAll(
- getOpUsageDataByDevice(includeMicrophoneUsage,
- persistentDeviceIds.valueAt(index)));
- }
-
return allUsages;
}
diff --git a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
index 85a13c7..bc03400 100644
--- a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
+++ b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java
@@ -27,7 +27,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ObservableServiceConnection;
-import com.android.internal.util.PersistentServiceConnection;
import java.util.ArrayList;
import java.util.List;
@@ -48,22 +47,20 @@
private static final int MSG_OVERLAY_CLIENT_READY = 3;
private final Handler mHandler;
- private final PersistentServiceConnection<IDreamOverlay> mConnection;
+ private final ObservableServiceConnection<IDreamOverlay> mConnection;
// Retrieved Client
private IDreamOverlayClient mClient;
// A list of pending requests to execute on the overlay.
private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
private final OverlayConnectionCallback mCallback;
+ private final Runnable mOnDisconnected;
DreamOverlayConnectionHandler(
Context context,
Looper looper,
Intent serviceIntent,
- int minConnectionDurationMs,
- int maxReconnectAttempts,
- int baseReconnectDelayMs) {
- this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts,
- baseReconnectDelayMs, new Injector());
+ Runnable onDisconnected) {
+ this(context, looper, serviceIntent, onDisconnected, new Injector());
}
@VisibleForTesting
@@ -71,20 +68,15 @@
Context context,
Looper looper,
Intent serviceIntent,
- int minConnectionDurationMs,
- int maxReconnectAttempts,
- int baseReconnectDelayMs,
+ Runnable onDisconnected,
Injector injector) {
mCallback = new OverlayConnectionCallback();
mHandler = new Handler(looper, new OverlayHandlerCallback());
+ mOnDisconnected = onDisconnected;
mConnection = injector.buildConnection(
context,
mHandler,
- serviceIntent,
- minConnectionDurationMs,
- maxReconnectAttempts,
- baseReconnectDelayMs
- );
+ serviceIntent);
}
/**
@@ -201,10 +193,14 @@
@Override
public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
int reason) {
+ Log.i(TAG, "Dream overlay disconnected, reason: " + reason);
mClient = null;
// Cancel any pending messages about the overlay being ready, since it is no
// longer ready.
mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY);
+ if (mOnDisconnected != null) {
+ mOnDisconnected.run();
+ }
}
}
@@ -217,25 +213,18 @@
* Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden
* in tests with a fake clock.
*/
- public PersistentServiceConnection<IDreamOverlay> buildConnection(
+ public ObservableServiceConnection<IDreamOverlay> buildConnection(
Context context,
Handler handler,
- Intent serviceIntent,
- int minConnectionDurationMs,
- int maxReconnectAttempts,
- int baseReconnectDelayMs) {
+ Intent serviceIntent) {
final Executor executor = handler::post;
final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
- return new PersistentServiceConnection<>(
+ return new ObservableServiceConnection<>(
context,
executor,
- handler,
IDreamOverlay.Stub::asInterface,
serviceIntent,
- flags,
- minConnectionDurationMs,
- maxReconnectAttempts,
- baseReconnectDelayMs
+ flags
);
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index a769643..8ecb1fb 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -182,6 +182,7 @@
/**
* The name of the dream manager service.
+ *
* @hide
*/
public static final String DREAM_SERVICE = "dreams";
@@ -222,12 +223,14 @@
/**
* Dream category for Low Light Dream
+ *
* @hide
*/
public static final int DREAM_CATEGORY_LOW_LIGHT = 1 << 0;
/**
* Dream category for Home Panel Dream
+ *
* @hide
*/
public static final int DREAM_CATEGORY_HOME_PANEL = 1 << 1;
@@ -295,7 +298,8 @@
void init(Context context);
/** Creates and returns the dream overlay connection */
- DreamOverlayConnectionHandler createOverlayConnection(ComponentName overlayComponent);
+ DreamOverlayConnectionHandler createOverlayConnection(ComponentName overlayComponent,
+ Runnable onDisconnected);
/** Returns the {@link DreamActivity} component */
ComponentName getDreamActivityComponent();
@@ -333,16 +337,15 @@
@Override
public DreamOverlayConnectionHandler createOverlayConnection(
- ComponentName overlayComponent) {
+ ComponentName overlayComponent,
+ Runnable onDisconnected) {
final Resources resources = mContext.getResources();
return new DreamOverlayConnectionHandler(
/* context= */ mContext,
Looper.getMainLooper(),
new Intent().setComponent(overlayComponent),
- resources.getInteger(R.integer.config_minDreamOverlayDurationMs),
- resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts),
- resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs));
+ onDisconnected);
}
@Override
@@ -1176,7 +1179,8 @@
// Connect to the overlay service if present.
if (!mWindowless && overlayComponent != null) {
- mOverlayConnection = mInjector.createOverlayConnection(overlayComponent);
+ mOverlayConnection = mInjector.createOverlayConnection(overlayComponent,
+ this::finish);
if (!mOverlayConnection.bind()) {
// Binding failed.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8271caf..3161ff1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -31,7 +31,6 @@
import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange;
import static com.android.window.flags.Flags.offloadColorExtraction;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -302,13 +301,10 @@
final InsetsState mInsetsState = new InsetsState();
final InsetsSourceControl.Array mTempControls = new InsetsSourceControl.Array();
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
- final Bundle mSyncSeqIdBundle = windowSessionRelayoutInfo() ? null : new Bundle();
SurfaceControl mSurfaceControl = new SurfaceControl();
- WindowRelayoutResult mRelayoutResult = windowSessionRelayoutInfo()
- ? new WindowRelayoutResult(mWinFrames, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls)
- : null;
+ WindowRelayoutResult mRelayoutResult = new WindowRelayoutResult(
+ mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls);
private final Point mSurfaceSize = new Point();
private final Point mLastSurfaceSize = new Point();
@@ -1277,15 +1273,8 @@
} else {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
- final int relayoutResult;
- if (windowSessionRelayoutInfo()) {
- relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, 0, 0, mRelayoutResult);
- } else {
- relayoutResult = mSession.relayoutLegacy(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
- mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
- }
+ final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
+ View.VISIBLE, 0, 0, 0, mRelayoutResult);
final Rect outMaxBounds = mMergedConfiguration.getMergedConfiguration()
.windowConfiguration.getMaxBounds();
if (!outMaxBounds.equals(maxBounds)) {
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index e3e4fc0..070d33b 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -49,18 +49,6 @@
*/
interface IWindowSession {
- /**
- * Bundle key to store the latest sync seq id for the relayout configuration.
- * @see #relayout
- */
- const String KEY_RELAYOUT_BUNDLE_SEQID = "seqid";
- /**
- * Bundle key to store the latest ActivityWindowInfo associated with the relayout configuration.
- * Will only be set if the relayout window is an activity window.
- * @see #relayout
- */
- const String KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO = "activity_window_info";
-
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, int requestedVisibleTypes,
out InputChannel outInputChannel, out InsetsState insetsState,
@@ -85,16 +73,6 @@
void remove(IBinder clientToken);
/**
- * @deprecated
- */
- int relayoutLegacy(IWindow window, in WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
- out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
- out InsetsState insetsState, out InsetsSourceControl.Array activeControls,
- out Bundle bundle);
-
- /**
* Change the parameters of a window. You supply the
* new parameters, it returns the new frame of the window on screen (the
* position should be ignored) and surface of the window. The surface
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9f4d7e2..1494d21 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -25,8 +25,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED;
-import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
@@ -90,6 +88,7 @@
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
@@ -114,6 +113,7 @@
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.addSchandleToVriSurface;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
@@ -124,12 +124,11 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import static com.android.window.flags.Flags.insetsControlChangedItem;
import static com.android.window.flags.Flags.setScPropertiesInClient;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
-import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -179,7 +178,6 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
-import android.hardware.SyncFence;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerGlobal;
@@ -222,7 +220,6 @@
import android.view.InputDevice.InputSourceClass;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceControl.TransactionStats;
import android.view.View.AttachInfo;
import android.view.View.FocusDirection;
import android.view.View.MeasureSpec;
@@ -298,7 +295,6 @@
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -1138,7 +1134,7 @@
*/
/**
- * A temporary object used so relayoutWindow can return the latest SyncSeqId
+ * Object for relayoutWindow to return the latest window info, including the SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
* window, and actually synchronous relayout window presents a problem. We could have
* a sequence like this:
@@ -1152,14 +1148,8 @@
* we get rid of synchronous relayout, until then, we use this bundle to channel the
* integer back over relayout.
*/
- private final Bundle mRelayoutBundle = windowSessionRelayoutInfo()
- ? null
- : new Bundle();
-
- private final WindowRelayoutResult mRelayoutResult = windowSessionRelayoutInfo()
- ? new WindowRelayoutResult(mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
- mTempInsets, mTempControls)
- : null;
+ private final WindowRelayoutResult mRelayoutResult = new WindowRelayoutResult(
+ mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls);
private static volatile boolean sAnrReported = false;
static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
@@ -1195,13 +1185,6 @@
private String mFpsTraceName;
private String mLargestViewTraceName;
- private final boolean mAppStartInfoTimestampsFlagValue;
- @GuardedBy("this")
- private boolean mAppStartTimestampsSent = false;
- private boolean mAppStartTrackingStarted = false;
- private long mRenderThreadDrawStartTimeNs = -1;
- private long mFirstFramePresentedTimeNs = -1;
-
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1319,8 +1302,6 @@
} else {
mSensitiveContentProtectionService = null;
}
-
- mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -2596,12 +2577,6 @@
notifySurfaceDestroyed();
}
destroySurface();
-
- // Reset so they can be sent again for warm starts.
- mAppStartTimestampsSent = false;
- mAppStartTrackingStarted = false;
- mRenderThreadDrawStartTimeNs = -1;
- mFirstFramePresentedTimeNs = -1;
}
}
}
@@ -4400,30 +4375,6 @@
reportDrawFinished(t, seqId);
}
});
-
- // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if
- // {link mTransactionCompletedTimeNs} has already been set.
- if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) {
- mAppStartTrackingStarted = true;
- Transaction transaction = new Transaction();
- transaction.addTransactionCompletedListener(mExecutor,
- new Consumer<TransactionStats>() {
- @Override
- public void accept(TransactionStats transactionStats) {
- SyncFence presentFence = transactionStats.getPresentFence();
- if (presentFence.awaitForever()) {
- if (mFirstFramePresentedTimeNs == -1) {
- // Only trigger once per {@link ViewRootImpl} instance.
- mFirstFramePresentedTimeNs = presentFence.getSignalTime();
- maybeSendAppStartTimes();
- }
- }
- presentFence.close();
- }
- });
- applyTransactionOnDraw(transaction);
- }
-
if (DEBUG_BLAST) {
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
}
@@ -4431,45 +4382,6 @@
mWmsRequestSyncGroup.add(this, null /* runnable */);
}
- private void maybeSendAppStartTimes() {
- synchronized (this) {
- if (mAppStartTimestampsSent) {
- // Don't send timestamps more than once.
- return;
- }
-
- // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not
- // post to main thread and check if we have it there.
- if (mRenderThreadDrawStartTimeNs != -1) {
- sendAppStartTimesLocked();
- } else {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- synchronized (ViewRootImpl.this) {
- if (mRenderThreadDrawStartTimeNs == -1) {
- return;
- }
- sendAppStartTimesLocked();
- }
- }
- });
- }
- }
- }
-
- @GuardedBy("this")
- private void sendAppStartTimesLocked() {
- try {
- ActivityManager.getService().reportStartInfoViewTimestamps(
- mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs);
- mAppStartTimestampsSent = true;
- } catch (RemoteException e) {
- // Ignore, timestamps may be lost.
- if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e);
- }
- }
-
/**
* Helper used to notify the service to block projection when a sensitive
* view (the view displays sensitive content) is attached to the window.
@@ -5656,13 +5568,7 @@
registerCallbackForPendingTransactions();
}
- long timeNs = SystemClock.uptimeNanos();
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
-
- // Only trigger once per {@link ViewRootImpl} instance.
- if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) {
- mRenderThreadDrawStartTimeNs = timeNs;
- }
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
@@ -9261,42 +9167,19 @@
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
mLastSyncSeqId);
} else {
- if (windowSessionRelayoutInfo()) {
- relayoutResult = mWindowSession.relayout(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
- } else {
- relayoutResult = mWindowSession.relayoutLegacy(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mRelayoutSeq, mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration,
- mSurfaceControl, mTempInsets, mTempControls, mRelayoutBundle);
- }
+ relayoutResult = mWindowSession.relayout(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+ mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
mRelayoutRequested = true;
if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
- ActivityWindowInfo outInfo = null;
- if (windowSessionRelayoutInfo()) {
- outInfo = mRelayoutResult != null ? mRelayoutResult.activityWindowInfo : null;
- } else {
- try {
- outInfo = mRelayoutBundle.getParcelable(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO,
- ActivityWindowInfo.class);
- mRelayoutBundle.remove(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO);
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to get ActivityWindowInfo from relayout Bundle", e);
- }
- }
+ final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo;
if (outInfo != null) {
mPendingActivityWindowInfo.set(outInfo);
}
}
- final int maybeSyncSeqId = windowSessionRelayoutInfo()
- ? mRelayoutResult.syncSeqId
- : mRelayoutBundle.getInt(IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID);
+ final int maybeSyncSeqId = mRelayoutResult.syncSeqId;
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d7d764b..55f22a6 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -23,7 +23,6 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -349,18 +348,6 @@
}
@Override
- public int relayoutLegacy(IWindow window, WindowManager.LayoutParams inAttrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
- int lastSyncSeqId, ClientWindowFrames outFrames,
- MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
- Bundle outSyncSeqIdBundle) {
- return relayoutInner(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags,
- seq, lastSyncSeqId, outFrames, outMergedConfiguration, outSurfaceControl,
- outInsetsState, outActiveControls);
- }
-
- @Override
public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
int lastSyncSeqId, WindowRelayoutResult outRelayoutResult) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 95d001f..d0bc57b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -100,6 +100,20 @@
flag {
namespace: "accessibility"
+ name: "global_action_menu"
+ description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
+ bug: "334954140"
+}
+
+flag {
+ namespace: "accessibility"
+ name: "global_action_media_play_pause"
+ description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
+ bug: "334954140"
+}
+
+flag {
+ namespace: "accessibility"
name: "granular_scrolling"
is_exported: true
description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
index fd73d35..0472b6c4 100644
--- a/core/java/android/window/flags/OWNERS
+++ b/core/java/android/window/flags/OWNERS
@@ -1,3 +1,4 @@
per-file responsible_apis.aconfig = file:/BAL_OWNERS
per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS
per-file accessibility.aconfig = file:/core/java/android/view/accessibility/OWNERS
+per-file lse_desktop_experience.aconfig = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index ca125da..d0ab674 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -120,3 +120,10 @@
description: "Whether to enable min/max window size constraints when resizing a window in desktop windowing mode"
bug: "327589741"
}
+
+flag {
+ name: "show_desktop_windowing_dev_option"
+ namespace: "lse_desktop_experience"
+ description: "Whether to show developer option for enabling desktop windowing mode"
+ bug: "348193756"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index d54ec5c..8cd2a3e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -103,17 +103,6 @@
flag {
namespace: "windowing_sdk"
- name: "window_session_relayout_info"
- description: "Pass an out RelayoutInfo instead of Bundle to fix the Parcel recycle bug"
- bug: "335601427"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "fix_pip_restore_to_overlay"
description: "Restore exit-pip activity back to ActivityEmbedding overlay"
bug: "297887697"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index fc3cd45..c8d6194 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -29,8 +29,6 @@
import android.content.ComponentName;
import android.os.Bundle;
import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.GridView;
import android.widget.TextView;
@@ -73,16 +71,11 @@
promptPrologue.setText(isTouchExploreOn
? R.string.accessibility_gesture_3finger_prompt_text
: R.string.accessibility_gesture_prompt_text);
- }
- if (TextUtils.isEmpty(component)) {
final TextView prompt = findViewById(R.id.accessibility_button_prompt);
- if (isGestureNavigateEnabled) {
- prompt.setText(isTouchExploreOn
- ? R.string.accessibility_gesture_3finger_instructional_text
- : R.string.accessibility_gesture_instructional_text);
- }
- prompt.setVisibility(View.VISIBLE);
+ prompt.setText(isTouchExploreOn
+ ? R.string.accessibility_gesture_3finger_instructional_text
+ : R.string.accessibility_gesture_instructional_text);
}
mTargets.addAll(getTargets(this, SOFTWARE));
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index a52d33a..618f622 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -155,8 +155,13 @@
*/
public static final int CUJ_FOLD_ANIM = 105;
+ /**
+ * Track window re-sizing interaction in desktop mode.
+ */
+ public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_FOLD_ANIM;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_RESIZE_WINDOW;
/** @hide */
@IntDef({
@@ -253,7 +258,8 @@
CUJ_LAUNCHER_PRIVATE_SPACE_LOCK,
CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK,
CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
- CUJ_FOLD_ANIM
+ CUJ_FOLD_ANIM,
+ CUJ_DESKTOP_MODE_RESIZE_WINDOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -361,6 +367,7 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
}
private Cuj() {
@@ -567,6 +574,8 @@
return "DESKTOP_MODE_MAXIMIZE_WINDOW";
case CUJ_FOLD_ANIM:
return "FOLD_ANIM";
+ case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
+ return "DESKTOP_MODE_RESIZE_WINDOW";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 13efaf0..754f77e7 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -28,6 +28,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN;
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
@@ -227,7 +228,8 @@
public static final int ACTION_NOTIFICATION_BIG_PICTURE_LOADED = 23;
/**
- * Time it takes to unlock the device via udfps, until the whole launcher appears.
+ * Time it takes to unlock the device via fps,
+ * until either the launcher or the foreground app appears.
*/
public static final int ACTION_KEYGUARD_FPS_UNLOCK_TO_HOME = 24;
@@ -249,6 +251,12 @@
*/
public static final int ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN = 27;
+ /**
+ * Time it takes to unlock the device via face,
+ * until either the launcher or the foreground app appears.
+ */
+ public static final int ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME = 28;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -278,6 +286,7 @@
ACTION_BACK_SYSTEM_ANIMATION,
ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
+ ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
};
/** @hide */
@@ -310,6 +319,7 @@
ACTION_BACK_SYSTEM_ANIMATION,
ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
+ ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -345,6 +355,7 @@
UIACTION_LATENCY_REPORTED__ACTION__ACTION_BACK_SYSTEM_ANIMATION,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN,
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME,
};
private final Object mLock = new Object();
@@ -539,6 +550,8 @@
return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN:
return "ACTION_NOTIFICATIONS_HIDDEN_FOR_MEASURE_WITH_SHADE_OPEN";
+ case UIACTION_LATENCY_REPORTED__ACTION__ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME:
+ return "ACTION_KEYGUARD_FACE_UNLOCK_TO_HOME";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/widget/NotificationCloseButton.java b/core/java/com/android/internal/widget/NotificationCloseButton.java
new file mode 100644
index 0000000..bce266d
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationCloseButton.java
@@ -0,0 +1,103 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+/**
+ * A close button in a notification
+ */
+@RemoteViews.RemoteView
+public class NotificationCloseButton extends ImageView {
+
+ @ColorInt private int mBackgroundColor;
+ @ColorInt private int mForegroundColor;
+
+ public NotificationCloseButton(Context context) {
+ this(context, null, 0, 0);
+ }
+
+ public NotificationCloseButton(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0, 0);
+ }
+
+ public NotificationCloseButton(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationCloseButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setContentDescription(mContext.getText(R.string.close_button_text));
+ boolean notificationCloseButtonSupported = Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_notificationCloseButtonSupported);
+ this.setVisibility(notificationCloseButtonSupported ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(Button.class.getName());
+ }
+
+
+ private void updateColors() {
+ if (mBackgroundColor != 0) {
+ this.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+ }
+ if (mForegroundColor != 0) {
+ this.setImageTintList(ColorStateList.valueOf(mForegroundColor));
+ }
+ }
+
+ /**
+ * Set the color used for the foreground.
+ */
+ @RemotableViewMethod
+ public void setForegroundColor(@ColorInt int color) {
+ mForegroundColor = color;
+ updateColors();
+ }
+
+ /**
+ * Sets the color used for the background.
+ */
+ @RemotableViewMethod
+ public void setBackgroundColor(@ColorInt int color) {
+ mBackgroundColor = color;
+ updateColors();
+ }
+}
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 1d9b0db..5a4d6db 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -146,6 +146,7 @@
IGNORED_FROM_VIRTUAL_DEVICE = 26;
IGNORED_ON_WIRELESS_CHARGER = 27;
IGNORED_MISSING_PERMISSION = 28;
+ CANCELLED_BY_APP_OPS = 29;
reserved 17; // prev IGNORED_UNKNOWN_VIBRATION
}
}
diff --git a/core/res/res/drawable/notification_close_button_icon.xml b/core/res/res/drawable/notification_close_button_icon.xml
new file mode 100644
index 0000000..947cd5a
--- /dev/null
+++ b/core/res/res/drawable/notification_close_button_icon.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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="@dimen/notification_close_button_size"
+ android:height="@dimen/notification_close_button_size"
+ android:viewportWidth="16.0"
+ android:viewportHeight="16.0">
+<path
+ android:fillColor="#FF000000"
+ android:pathData="M 12.6667 4.2733 L 11.7267 3.3333 L 8 7.06 L 4.2734 3.3333 L 3.3334
+4.2733 L 7.06 8 L 3.3334 11.7267 L 4.2734 12.6667 L 8 8.94 L 11.7267 12.6667 L 12.6667
+11.7267 L 8.94 8 L 12.6667 4.2733 Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
index 2f97bae..f50af15 100644
--- a/core/res/res/layout/accessibility_button_chooser.xml
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -47,6 +47,16 @@
android:paddingTop="8dp"
android:paddingBottom="8dp"/>
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/accessibility_button_prompt"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:text="@string/accessibility_button_instructional_text"
+ android:gravity="start|center_vertical"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"/>
+
<GridView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -57,16 +67,5 @@
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
android:gravity="center"/>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/accessibility_button_prompt"
- android:textAppearance="?attr/textAppearanceMedium"
- android:text="@string/accessibility_button_instructional_text"
- android:gravity="start|center_vertical"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:visibility="gone"/>
</LinearLayout>
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/notification_close_button.xml b/core/res/res/layout/notification_close_button.xml
new file mode 100644
index 0000000..5eff84e
--- /dev/null
+++ b/core/res/res/layout/notification_close_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+
+<com.android.internal.widget.NotificationCloseButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:contentDescription="@string/close_button_text"
+ android:visibility="gone"
+ android:src="@drawable/notification_close_button_icon"
+ android:padding="2dp"
+ android:scaleType="fitCenter"
+ android:importantForAccessibility="no"
+ >
+</com.android.internal.widget.NotificationCloseButton>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index d80b765..e44c727 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -83,11 +83,28 @@
android:focusable="false"
/>
- <include layout="@layout/notification_expand_button"
+ <LinearLayout
+ android:id="@+id/notification_buttons_column"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- />
+ android:orientation="vertical"
+ >
+
+ <include layout="@layout/notification_close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="end"
+ android:layout_marginEnd="20dp"
+ />
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ />
+
+ </LinearLayout>
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 452df50..29f14a4 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -157,20 +157,38 @@
android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
+ <LinearLayout
+ android:id="@+id/notification_buttons_column"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="@dimen/notification_content_margin_end"
+ android:layout_alignParentEnd="true"
+ android:orientation="vertical"
>
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
+ <include layout="@layout/notification_close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="end"
+ android:layout_marginEnd="20dp"
/>
- </FrameLayout>
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index ce8a904..13f2c37 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -107,13 +107,20 @@
>
<!--expand_button_container is dynamically placed between here and at the end of the
layout. It starts here since only FrameLayout layout params have gravity-->
- <FrameLayout
+ <LinearLayout
android:id="@+id/expand_button_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end|top"
android:clipChildren="false"
- android:clipToPadding="false">
+ android:clipToPadding="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="end"
+ android:layout_marginEnd="20dp"
+ />
<!--expand_button_touch_container makes sure that we can nicely center the expand
content in the collapsed layout while the parent makes sure that we're never laid out
bigger than the messaging content.-->
@@ -145,6 +152,6 @@
android:layout_gravity="center"
/>
</LinearLayout>
- </FrameLayout>
+ </LinearLayout>
</FrameLayout>
</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 678dcff..105b099 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -567,14 +567,6 @@
It has been updated to affect other plug types. -->
<bool name="config_keepDreamingWhenUnplugging">false</bool>
- <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
- it becomes disconnected -->
- <integer name="config_dreamOverlayReconnectTimeoutMs">1000</integer> <!-- 1 second -->
- <!-- The maximum number of times to attempt reconnecting to the dream overlay service -->
- <integer name="config_dreamOverlayMaxReconnectAttempts">3</integer>
- <!-- The duration after which the dream overlay connection should be considered stable -->
- <integer name="config_minDreamOverlayDurationMs">10000</integer> <!-- 10 seconds -->
-
<!-- Auto-rotation behavior -->
<!-- If true, enables auto-rotation features using the accelerometer.
@@ -7092,6 +7084,9 @@
<!-- Whether the system uses auto-suspend mode. -->
<bool name="config_useAutoSuspend">true</bool>
+ <!-- Whether close/dismiss buttons are supported on notifications. -->
+ <bool name="config_notificationCloseButtonSupported">false</bool>
+
<!-- Whether to show GAIA education screen during account login of private space setup.
OEM/Partner can explicitly opt to disable the screen. -->
<bool name="config_enableGaiaEducationInPrivateSpace">true</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5fea515..6cba84b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -355,6 +355,9 @@
<!-- the padding of the expand icon in the notification header -->
<dimen name="notification_expand_button_icon_padding">2dp</dimen>
+ <!-- the size of the notification close button -->
+ <dimen name="notification_close_button_size">16dp</dimen>
+
<!-- Vertical margin for the headerless notification content, when content has 1 line -->
<!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
<dimen name="notification_headerless_margin_oneline">16dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 351cbad..05c46b9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4844,19 +4844,19 @@
<!-- Text spoken when accessibility shortcut warning dialog is shown. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_spoken_feedback">Release the volume keys. To turn on <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>, press and hold both volume keys again for 3 seconds.</string>
- <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. [CHAR LIMIT=none]-->
- <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the accessibility button:</string>
+ <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar or in gesture navigation. [CHAR LIMIT=none]-->
+ <string name="accessibility_button_prompt_text">Choose a feature</string>
<!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled [CHAR LIMIT=none] -->
- <string name="accessibility_gesture_prompt_text">Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):</string>
+ <string name="accessibility_gesture_prompt_text">Choose a feature</string>
<!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled [CHAR LIMIT=none] -->
- <string name="accessibility_gesture_3finger_prompt_text">Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):</string>
+ <string name="accessibility_gesture_3finger_prompt_text">Choose a feature</string>
<!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. [CHAR LIMIT=none]-->
- <string name="accessibility_button_instructional_text">To switch between features, touch & hold the accessibility button.</string>
+ <string name="accessibility_button_instructional_text">The feature will open next time you tap the accessibility button.</string>
<!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation is enabled. [CHAR LIMIT=none] -->
- <string name="accessibility_gesture_instructional_text">To switch between features, swipe up with two fingers and hold.</string>
+ <string name="accessibility_gesture_instructional_text">The feature will open next time you use this shortcut. Swipe up with two fingers from the bottom of your screen and release quickly.</string>
<!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button when gesture navigation and TalkBack is enabled. [CHAR LIMIT=none] -->
- <string name="accessibility_gesture_3finger_instructional_text">To switch between features, swipe up with three fingers and hold.</string>
+ <string name="accessibility_gesture_3finger_instructional_text">The feature will open next time you use this shortcut. Swipe up with three fingers from the bottom of your screen and release quickly.</string>
<!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
<string name="accessibility_magnification_chooser_text">Magnification</string>
@@ -5985,6 +5985,10 @@
<string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
<!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
+ <!-- Label for menu action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_menu_label">Menu</string>
+ <!-- Label for media play/pause action [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_media_play_pause_label">Media Play/Pause</string>
<!-- Label for Dpad up action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dpad_up_label">Dpad Up</string>
<!-- Label for Dpad down action [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 63934c4..0f54d89 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -521,6 +521,7 @@
<java-symbol type="bool" name="config_preferKeepClearForFocus" />
<java-symbol type="bool" name="config_hibernationDeletesOatArtifactsEnabled"/>
<java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/>
+ <java-symbol type="bool" name="config_notificationCloseButtonSupported"/>
<java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
<java-symbol type="color" name="tab_indicator_text_v4" />
@@ -2298,9 +2299,6 @@
<java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
<java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" />
- <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
- <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
- <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
<java-symbol type="array" name="config_loggable_dream_prefixes" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
@@ -3171,6 +3169,7 @@
<java-symbol type="id" name="header_text_secondary_divider" />
<java-symbol type="drawable" name="ic_expand_notification" />
<java-symbol type="drawable" name="ic_collapse_notification" />
+ <java-symbol type="drawable" name="notification_close_button_icon" />
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="drawable" name="ic_notification_summary_auto" />
@@ -3877,6 +3876,7 @@
<java-symbol type="string" name="expand_button_content_description_collapsed" />
<java-symbol type="string" name="expand_button_content_description_expanded" />
+ <java-symbol type="string" name="close_button_text" />
<java-symbol type="string" name="content_description_collapsed" />
<java-symbol type="string" name="content_description_expanded" />
@@ -4462,6 +4462,8 @@
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
<java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
+ <java-symbol type="string" name="accessibility_system_action_menu_label" />
+ <java-symbol type="string" name="accessibility_system_action_media_play_pause_label" />
<java-symbol type="string" name="accessibility_system_action_dpad_up_label" />
<java-symbol type="string" name="accessibility_system_action_dpad_down_label" />
<java-symbol type="string" name="accessibility_system_action_dpad_left_label" />
@@ -5042,6 +5044,9 @@
<java-symbol type="string" name="ui_translation_accessibility_translation_finished" />
<java-symbol type="layout" name="notification_expand_button"/>
+ <java-symbol type="id" name="close_button" />
+ <java-symbol type="layout" name="notification_close_button"/>
+ <java-symbol type="id" name="notification_buttons_column" />
<java-symbol type="bool" name="config_supportsMicToggle" />
<java-symbol type="bool" name="config_supportsCamToggle" />
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 24aea37..ecf4eb4 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -17,7 +17,6 @@
package android.security;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -112,29 +111,6 @@
}
/**
- * Informs Keystore 2.0 about changing user's password
- *
- * @param userId - Android user id of the user
- * @param password - a secret derived from the synthetic password provided by the
- * LockSettingsService
- * @return 0 if successful or a {@code ResponseCode}
- * @hide
- */
- public static int onUserPasswordChanged(int userId, @Nullable byte[] password) {
- StrictMode.noteDiskWrite();
- try {
- getService().onUserPasswordChanged(userId, password);
- return 0;
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "onUserPasswordChanged failed", e);
- return e.errorCode;
- } catch (Exception e) {
- Log.e(TAG, "Can not connect to keystore", e);
- return SYSTEM_ERROR;
- }
- }
-
- /**
* Tells Keystore that a user's LSKF is being removed, ie the user's lock screen is changing to
* Swipe or None. Keystore uses this notification to delete the user's auth-bound keys.
*
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 34f03c2..501bedd 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
- android:id="@+id/bubble_bar_expanded_view">
+ android:id="@+id/bubble_expanded_view">
<com.android.wm.shell.bubbles.bar.BubbleBarHandleView
android:id="@+id/bubble_bar_handle_view"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index 6ffeb97..58007b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -27,7 +27,9 @@
import android.util.Size;
import android.view.Gravity;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -39,6 +41,9 @@
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
+ // The same value (with the same name) is used in Launcher.
+ private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
+
@NonNull private final PipBoundsState mPipBoundsState;
@NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
@NonNull protected final SizeSpecSource mSizeSpecSource;
@@ -206,9 +211,27 @@
*/
public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
Rect destinationBounds) {
- return sourceRectHint != null
- && sourceRectHint.width() > destinationBounds.width()
- && sourceRectHint.height() > destinationBounds.height();
+ if (sourceRectHint == null || sourceRectHint.isEmpty()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, empty hint");
+ return false;
+ }
+ if (sourceRectHint.width() <= destinationBounds.width()
+ || sourceRectHint.height() <= destinationBounds.height()) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, hint(%s) is smaller"
+ + " than destination(%s)", sourceRectHint, destinationBounds);
+ return false;
+ }
+ final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height();
+ final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height();
+ if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "isSourceRectHintValidForEnterPip=false, hint(%s) does not match"
+ + " destination(%s) aspect ratio", sourceRectHint, destinationBounds);
+ return false;
+ }
+ return true;
}
public float getDefaultAspectRatio() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index a09720d..3e9366f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -34,6 +34,7 @@
import com.android.wm.shell.Flags
import com.android.wm.shell.protolog.ShellProtoLogGroup
import kotlin.math.abs
+import kotlin.math.roundToInt
/** A class that includes convenience methods. */
object PipUtils {
@@ -149,16 +150,16 @@
val appBoundsAspRatio = appBounds.width().toFloat() / appBounds.height()
val width: Int
val height: Int
- var left = 0
- var top = 0
+ var left = appBounds.left
+ var top = appBounds.top
if (appBoundsAspRatio < aspectRatio) {
width = appBounds.width()
- height = Math.round(width / aspectRatio)
- top = (appBounds.height() - height) / 2
+ height = (width / aspectRatio).roundToInt()
+ top = appBounds.top + (appBounds.height() - height) / 2
} else {
height = appBounds.height()
- width = Math.round(height * aspectRatio)
- left = (appBounds.width() - width) / 2
+ width = (height * aspectRatio).roundToInt()
+ left = appBounds.left + (appBounds.width() - width) / 2
}
return Rect(left, top, left + width, top + height)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index 1385f42..7ad68aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -5,3 +5,4 @@
nmusgrave@google.com
pbdr@google.com
tkachenkoi@google.com
+vaniadesmonda@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 202f60d..3d1994c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -137,7 +137,7 @@
mTmpDestinationRect.inset(insets);
// Scale to the bounds no smaller than the destination and offset such that the top/left
// of the scaled inset source rect aligns with the top/left of the destination bounds
- final float scale, left, top;
+ final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
// scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
@@ -148,14 +148,17 @@
? (float) destinationBounds.width() / sourceBounds.width()
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
- left = destinationBounds.left - insets.left * scale;
- top = destinationBounds.top - insets.top * scale;
} else {
scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
(float) destinationBounds.height() / sourceBounds.height());
- // Work around the rounding error by fix the position at very beginning.
- left = scale == 1 ? 0 : destinationBounds.left - insets.left * scale;
- top = scale == 1 ? 0 : destinationBounds.top - insets.top * scale;
+ }
+ float left = destinationBounds.left - insets.left * scale;
+ float top = destinationBounds.top - insets.top * scale;
+ if (scale == 1) {
+ // Work around the 1 pixel off error by rounding the position down at very beginning.
+ // We noticed such error from flicker tests, not visually.
+ left = sourceBounds.left;
+ top = sourceBounds.top;
}
mTmpTransform.setScale(scale, scale);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 82add29..e2e1ecd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,7 +63,6 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.util.Rational;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -128,8 +127,6 @@
SystemProperties.getInt(
"persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
- private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f;
-
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
@@ -822,37 +819,6 @@
mPictureInPictureParams.getTitle());
mPipParamsChangedForwarder.notifySubtitleChanged(
mPictureInPictureParams.getSubtitle());
-
- if (mPictureInPictureParams.hasSourceBoundsHint()
- && mPictureInPictureParams.hasSetAspectRatio()) {
- Rational sourceRectHintAspectRatio = new Rational(
- mPictureInPictureParams.getSourceRectHint().width(),
- mPictureInPictureParams.getSourceRectHint().height());
- if (sourceRectHintAspectRatio.compareTo(
- mPictureInPictureParams.getAspectRatio()) != 0) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "Aspect ratio of source rect hint (%d/%d) does not match the provided "
- + "aspect ratio value (%d/%d). Consider matching them for "
- + "improved animation. Future releases might override the "
- + "value to match.",
- mPictureInPictureParams.getSourceRectHint().width(),
- mPictureInPictureParams.getSourceRectHint().height(),
- mPictureInPictureParams.getAspectRatio().getNumerator(),
- mPictureInPictureParams.getAspectRatio().getDenominator());
- }
- if (Math.abs(sourceRectHintAspectRatio.floatValue()
- - mPictureInPictureParams.getAspectRatioFloat())
- > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "Aspect ratio of source rect hint (%f) does not match the provided "
- + "aspect ratio value (%f) and is above threshold of %f. "
- + "Consider matching them for improved animation. Future "
- + "releases might override the value to match.",
- sourceRectHintAspectRatio.floatValue(),
- mPictureInPictureParams.getAspectRatioFloat(),
- PIP_ASPECT_RATIO_MISMATCH_THRESHOLD);
- }
- }
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 66b3553..8fc54ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -21,8 +21,6 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
-
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,7 +28,6 @@
import android.app.ActivityManager.TaskDescription;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
@@ -139,16 +136,10 @@
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- if (windowSessionRelayoutInfo()) {
- final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames,
- tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
- outRelayoutResult);
- } else {
- session.relayoutLegacy(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
- tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, new Bundle());
- }
+ final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames,
+ tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
+ outRelayoutResult);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5fce5d2..956d04c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -18,6 +18,8 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW;
+
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -33,6 +35,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
@@ -89,6 +92,10 @@
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
+ // Capture CUJ for re-sizing window in DW mode.
+ InteractionJankMonitorUtils.beginTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW,
+ mDesktopWindowDecoration.mContext, mDesktopWindowDecoration.mTaskSurface,
+ /* tag= */ null);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -146,6 +153,7 @@
// won't be called.
resetVeilIfVisible();
}
+ InteractionJankMonitorUtils.endTracing(CUJ_DESKTOP_MODE_RESIZE_WINDOW);
} else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 5880ffb..72950a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -88,8 +88,11 @@
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue1 = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 0d0af11..4d185c6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -28,8 +28,8 @@
#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
#include <ui/FatVector.h>
-#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
#include <sstream>
@@ -141,7 +141,8 @@
mPhysicalDeviceFeatures2 = {};
}
-void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) {
+void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions,
+ VkPhysicalDeviceFeatures2& features) {
VkResult err;
constexpr VkApplicationInfo app_info = {
@@ -506,7 +507,7 @@
return vkGetInstanceProcAddr(instance, proc_name);
};
- GrVkBackendContext backendContext;
+ skgpu::VulkanBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
backendContext.fDevice = mDevice;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b92ebb3..08f9d42 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -24,8 +24,7 @@
#include <SkSurface.h>
#include <android-base/unique_fd.h>
#include <utils/StrongPointer.h>
-#include <vk/GrVkBackendContext.h>
-#include <vk/GrVkExtensions.h>
+#include <vk/VulkanExtensions.h>
#include <vulkan/vulkan.h>
// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
@@ -127,7 +126,7 @@
// Sets up the VkInstance and VkDevice objects. Also fills out the passed in
// VkPhysicalDeviceFeatures struct.
- void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&);
+ void setupDevice(skgpu::VulkanExtensions&, VkPhysicalDeviceFeatures2&);
// simple wrapper class that exists only to initialize a pointer to NULL
template <typename FNPTR_TYPE>
@@ -206,7 +205,7 @@
BufferAge,
};
SwapBehavior mSwapBehavior = SwapBehavior::Discard;
- GrVkExtensions mExtensions;
+ skgpu::VulkanExtensions mExtensions;
uint32_t mDriverVersion = 0;
std::once_flag mInitFlag;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 386a606c..e134c23 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4069,8 +4069,10 @@
private boolean delegateSoundEffectToVdm(@SystemSoundEffect int effectType) {
if (hasCustomPolicyVirtualDeviceContext()) {
VirtualDeviceManager vdm = getVirtualDeviceManager();
- vdm.playSoundEffect(mOriginalContextDeviceId, effectType);
- return true;
+ if (vdm != null) {
+ vdm.playSoundEffect(mOriginalContextDeviceId, effectType);
+ return true;
+ }
}
return false;
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 0589c0f12..e048d5c 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -26,6 +26,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.net.Uri;
import android.os.Bundle;
@@ -813,6 +814,34 @@
|| mAllowedPackages.contains(packageName);
}
+ /**
+ * Returns whether this route's type can only be published by the system route provider.
+ *
+ * @see #isSystemRoute()
+ * @hide
+ */
+ // The default case catches all other types.
+ @SuppressLint("SwitchIntDef")
+ public boolean isSystemRouteType() {
+ return switch (mType) {
+ case TYPE_BUILTIN_SPEAKER,
+ TYPE_BLUETOOTH_A2DP,
+ TYPE_DOCK,
+ TYPE_BLE_HEADSET,
+ TYPE_HEARING_AID,
+ TYPE_HDMI,
+ TYPE_HDMI_ARC,
+ TYPE_HDMI_EARC,
+ TYPE_USB_ACCESSORY,
+ TYPE_USB_DEVICE,
+ TYPE_USB_HEADSET,
+ TYPE_WIRED_HEADPHONES,
+ TYPE_WIRED_HEADSET ->
+ true;
+ default -> false;
+ };
+ }
+
/** Returns the route suitability status. */
@SuitabilityStatus
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index cce3d4f..a14f1fd 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -475,9 +475,25 @@
*/
public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
Objects.requireNonNull(routes, "routes must not be null");
- mProviderInfo = new MediaRoute2ProviderInfo.Builder()
- .addRoutes(routes)
- .build();
+ List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size());
+
+ for (MediaRoute2Info route : routes) {
+ if (route.isSystemRouteType()) {
+ Log.w(
+ TAG,
+ "Attempting to add a system route type from a non-system route "
+ + "provider. Overriding type to TYPE_UNKNOWN. Route: "
+ + route);
+ sanitizedRoutes.add(
+ new MediaRoute2Info.Builder(route)
+ .setType(MediaRoute2Info.TYPE_UNKNOWN)
+ .build());
+ } else {
+ sanitizedRoutes.add(route);
+ }
+ }
+
+ mProviderInfo = new MediaRoute2ProviderInfo.Builder().addRoutes(sanitizedRoutes).build();
schedulePublishState();
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 5770c51..66ab81b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -16,10 +16,7 @@
package com.android.companiondevicemanager;
-import static android.companion.CompanionDeviceManager.REASON_CANCELED;
-import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
-import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
-import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
@@ -234,8 +231,7 @@
boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
if (forCancelDialog) {
Slog.i(TAG, "Cancelling the user confirmation");
- cancel(/* discoveryTimeOut */ false, /* userRejected */ false,
- /* internalError */ false);
+ cancel(RESULT_CANCELED);
return;
}
@@ -243,8 +239,14 @@
// yet). We can only "process" one request at a time.
final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
.asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
+
+ if (appCallback == null) {
+ return;
+ }
+ Slog.e(TAG, "More than one AssociationRequests are processing.");
+
try {
- requireNonNull(appCallback).onFailure("Busy.");
+ appCallback.onFailure(RESULT_INTERNAL_ERROR);
} catch (RemoteException ignore) {
}
}
@@ -255,8 +257,7 @@
// TODO: handle config changes without cancelling.
if (!isDone()) {
- cancel(/* discoveryTimeOut */ false,
- /* userRejected */ false, /* internalError */ false); // will finish()
+ cancel(RESULT_CANCELED); // will finish()
}
}
@@ -330,8 +331,7 @@
&& CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
synchronized (LOCK) {
if (sDiscoveryStarted) {
- cancel(/* discoveryTimeOut */ true,
- /* userRejected */ false, /* internalError */ false);
+ cancel(RESULT_DISCOVERY_TIMEOUT);
}
}
}
@@ -371,7 +371,7 @@
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
}
- private void cancel(boolean discoveryTimeout, boolean userRejected, boolean internalError) {
+ private void cancel(int failureCode) {
if (isDone()) {
Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
return;
@@ -379,35 +379,19 @@
mCancelled = true;
// Stop discovery service if it was used.
- if (!mRequest.isSelfManaged() || discoveryTimeout) {
+ if (!mRequest.isSelfManaged()) {
CompanionDeviceDiscoveryService.stop(this);
}
- final String cancelReason;
- final int resultCode;
- if (userRejected) {
- cancelReason = REASON_USER_REJECTED;
- resultCode = RESULT_USER_REJECTED;
- } else if (discoveryTimeout) {
- cancelReason = REASON_DISCOVERY_TIMEOUT;
- resultCode = RESULT_DISCOVERY_TIMEOUT;
- } else if (internalError) {
- cancelReason = REASON_INTERNAL_ERROR;
- resultCode = RESULT_INTERNAL_ERROR;
- } else {
- cancelReason = REASON_CANCELED;
- resultCode = CompanionDeviceManager.RESULT_CANCELED;
- }
-
// First send callback to the app directly...
try {
- Slog.i(TAG, "Sending onFailure to app due to reason=" + cancelReason);
- mAppCallback.onFailure(cancelReason);
+ Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
+ mAppCallback.onFailure(failureCode);
} catch (RemoteException ignore) {
}
// ... then set result and finish ("sending" onActivityResult()).
- setResultAndFinish(null, resultCode);
+ setResultAndFinish(null, failureCode);
}
private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -452,8 +436,7 @@
}
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
- cancel(/* discoveryTimeout */ false,
- /* userRejected */ false, /* internalError */ true);
+ cancel(RESULT_INTERNAL_ERROR);
return;
}
@@ -637,7 +620,7 @@
// Disable the button, to prevent more clicks.
v.setEnabled(false);
- cancel(/* discoveryTimeout */ false, /* userRejected */ true, /* internalError */ false);
+ cancel(RESULT_USER_REJECTED);
}
private void onShowHelperDialog(View view) {
@@ -763,7 +746,7 @@
@Override
public void onShowHelperDialogFailed() {
- cancel(/* discoveryTimeout */ false, /* userRejected */ false, /* internalError */ true);
+ cancel(RESULT_INTERNAL_ERROR);
}
@Override
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 5966c9f..62ed66c 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -11,3 +11,6 @@
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
+
+# Notification-related utilities
+per-file */notification/* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 32557b9..a158756 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -79,3 +79,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_determining_spatial_audio_attributes_by_profile"
+ namespace: "cross_device_experiences"
+ description: "Use bluetooth profile connection policy to determine spatial audio attributes"
+ bug: "341005211"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 20b949f4..8ec5ba1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -20,6 +20,7 @@
import android.database.ContentObserver
import android.media.AudioDeviceInfo
import android.media.AudioManager
+import android.media.AudioManager.AudioDeviceCategory
import android.media.AudioManager.OnCommunicationDeviceChangedListener
import android.provider.Settings
import androidx.concurrent.futures.DirectExecutor
@@ -85,6 +86,10 @@
suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
+
+ /** Gets audio device category. */
+ @AudioDeviceCategory
+ suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
}
class AudioRepositoryImpl(
@@ -211,6 +216,13 @@
withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
}
+ @AudioDeviceCategory
+ override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
+ return withContext(backgroundCoroutineContext) {
+ audioManager.getBluetoothAudioDeviceCategory(bluetoothAddress)
+ }
+ }
+
private fun getMinVolume(stream: AudioStream): Int =
try {
audioManager.getStreamMinVolume(stream.value)
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 683759d..844dc12 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -247,6 +247,19 @@
}
}
+ @Test
+ fun getBluetoothAudioDeviceCategory() {
+ testScope.runTest {
+ `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn(
+ AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
+
+ val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78")
+ runCurrent()
+
+ assertThat(category).isEqualTo(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
+ }
+ }
+
private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
verify(audioManager)
.addOnCommunicationDeviceChangedListener(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 18085ab..7d82920 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
@@ -41,8 +42,10 @@
private val interactionHandler: SmartspaceInteractionHandler,
private val dialogFactory: SystemUIDialogFactory,
private val lockSection: LockSection,
+ private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
) {
+
@Composable
fun SceneScope.Content(modifier: Modifier = Modifier) {
Layout(
@@ -65,10 +68,16 @@
modifier = Modifier.element(Communal.Elements.LockIcon)
)
}
+ with(bottomAreaSection) {
+ IndicationArea(
+ Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
+ )
+ }
}
) { measurables, constraints ->
val communalGridMeasurable = measurables[0]
val lockIconMeasurable = measurables[1]
+ val bottomAreaMeasurable = measurables[2]
val noMinConstraints =
constraints.copy(
@@ -85,6 +94,13 @@
bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
)
+ val bottomAreaPlaceable =
+ bottomAreaMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+
val communalGridPlaceable =
communalGridMeasurable.measure(
noMinConstraints.copy(maxHeight = lockIconBounds.top)
@@ -99,6 +115,10 @@
x = lockIconBounds.left,
y = lockIconBounds.top,
)
+ bottomAreaPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - bottomAreaPlaceable.height,
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 4dc801c..7062489 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -52,10 +52,12 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -75,12 +77,15 @@
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -153,7 +158,7 @@
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.launch
-@OptIn(ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun CommunalHub(
modifier: Modifier = Modifier,
@@ -378,6 +383,33 @@
onCancel = viewModel::onEnableWorkProfileDialogCancel
)
}
+
+ if (viewModel is CommunalEditModeViewModel) {
+ val showBottomSheet by viewModel.showDisclaimer.collectAsStateWithLifecycle(false)
+
+ if (showBottomSheet) {
+ val scope = rememberCoroutineScope()
+ val sheetState = rememberModalBottomSheetState()
+ val colors = LocalAndroidColorScheme.current
+
+ ModalBottomSheet(
+ onDismissRequest = viewModel::onDisclaimerDismissed,
+ sheetState = sheetState,
+ dragHandle = null,
+ containerColor = colors.surfaceContainer,
+ ) {
+ DisclaimerBottomSheetContent {
+ scope
+ .launch { sheetState.hide() }
+ .invokeOnCompletion {
+ if (!sheetState.isVisible) {
+ viewModel.onDisclaimerDismissed()
+ }
+ }
+ }
+ }
+ }
+ }
}
}
@@ -389,6 +421,47 @@
viewModel.signalUserInteraction()
}
+@Composable
+private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
+ val colors = LocalAndroidColorScheme.current
+
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 32.dp, vertical = 24.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = null,
+ tint = colors.primary,
+ modifier = Modifier.size(32.dp)
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = stringResource(R.string.communal_widgets_disclaimer_title),
+ style = MaterialTheme.typography.headlineMedium,
+ color = colors.onSurface,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = stringResource(R.string.communal_widgets_disclaimer_text),
+ color = colors.onSurfaceVariant,
+ )
+ Button(
+ modifier =
+ Modifier.padding(horizontal = 26.dp, vertical = 16.dp)
+ .widthIn(min = 200.dp)
+ .heightIn(min = 56.dp),
+ onClick = { onButtonClicked() }
+ ) {
+ Text(
+ stringResource(R.string.communal_widgets_disclaimer_button),
+ style = MaterialTheme.typography.labelLarge,
+ )
+ }
+ }
+}
+
/**
* Observes communal content and scrolls to any added or updated live content, e.g. a new media
* session is started, or a paused timer is resumed.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 97d5b41..86639fa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.DpSize
@@ -183,7 +184,7 @@
indicationController: KeyguardIndicationController,
modifier: Modifier = Modifier,
) {
- val (disposable, setDisposable) = mutableStateOf<DisposableHandle?>(null)
+ val (disposable, setDisposable) = remember { mutableStateOf<DisposableHandle?>(null) }
AndroidView(
factory = { context ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 3035481..68cfa28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -223,23 +222,14 @@
}
private fun givenAlternateBouncerSupported() {
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- kosmos.fingerprintPropertyRepository.supportsUdfps()
- } else {
- kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true)
- }
+ kosmos.givenAlternateBouncerSupported()
}
private fun givenCanShowAlternateBouncer() {
- givenAlternateBouncerSupported()
- kosmos.keyguardBouncerRepository.setPrimaryShow(false)
- kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- kosmos.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
- whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
+ kosmos.givenCanShowAlternateBouncer()
}
private fun givenCannotShowAlternateBouncer() {
- kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.givenCannotShowAlternateBouncer()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 5e120b5..a8bdc7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -18,8 +18,8 @@
import android.content.Context
import android.content.Intent
-import android.content.SharedPreferences
import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_MAIN
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -30,108 +30,87 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.fakeUserFileManager
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.FakeSharedPreferences
import com.google.common.truth.Truth.assertThat
-import java.io.File
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var tableLogBuffer: TableLogBuffer
-
- private lateinit var underTest: CommunalPrefsRepositoryImpl
-
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var userRepository: FakeUserRepository
- private lateinit var userFileManager: UserFileManager
+ private val userFileManager: UserFileManager = spy(kosmos.fakeUserFileManager)
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- userRepository = kosmos.fakeUserRepository
- userRepository.setUserInfos(USER_INFOS)
-
- userFileManager =
- FakeUserFileManager(
- mapOf(
- USER_INFOS[0].id to FakeSharedPreferences(),
- USER_INFOS[1].id to FakeSharedPreferences()
- )
- )
+ private val underTest: CommunalPrefsRepositoryImpl by lazy {
+ CommunalPrefsRepositoryImpl(
+ kosmos.testDispatcher,
+ userFileManager,
+ kosmos.broadcastDispatcher,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ )
}
@Test
fun isCtaDismissedValue_byDefault_isFalse() =
testScope.runTest {
- underTest = createCommunalPrefsRepositoryImpl(userFileManager)
- val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
assertThat(isCtaDismissed).isFalse()
}
@Test
fun isCtaDismissedValue_onSet_isTrue() =
testScope.runTest {
- underTest = createCommunalPrefsRepositoryImpl(userFileManager)
- val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
- underTest.setCtaDismissedForCurrentUser()
+ underTest.setCtaDismissed(MAIN_USER)
assertThat(isCtaDismissed).isTrue()
}
@Test
- fun isCtaDismissedValue_whenSwitchUser() =
+ fun isCtaDismissedValue_onSetForDifferentUser_isStillFalse() =
testScope.runTest {
- underTest = createCommunalPrefsRepositoryImpl(userFileManager)
- val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
- underTest.setCtaDismissedForCurrentUser()
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
- // dismissed true for primary user
- assertThat(isCtaDismissed).isTrue()
-
- // switch to secondary user
- userRepository.setSelectedUserInfo(USER_INFOS[1])
-
- // dismissed is false for secondary user
+ underTest.setCtaDismissed(SECONDARY_USER)
assertThat(isCtaDismissed).isFalse()
+ }
- // switch back to primary user
- userRepository.setSelectedUserInfo(USER_INFOS[0])
+ @Test
+ fun isDisclaimerDismissed_byDefault_isFalse() =
+ testScope.runTest {
+ val isDisclaimerDismissed by
+ collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
+ assertThat(isDisclaimerDismissed).isFalse()
+ }
- // dismissed is true for primary user
- assertThat(isCtaDismissed).isTrue()
+ @Test
+ fun isDisclaimerDismissed_onSet_isTrue() =
+ testScope.runTest {
+ val isDisclaimerDismissed by
+ collectLastValue(underTest.isDisclaimerDismissed(MAIN_USER))
+
+ underTest.setDisclaimerDismissed(MAIN_USER)
+ assertThat(isDisclaimerDismissed).isTrue()
}
@Test
fun getSharedPreferences_whenFileRestored() =
testScope.runTest {
- val userFileManagerSpy = Mockito.spy(userFileManager)
- underTest = createCommunalPrefsRepositoryImpl(userFileManagerSpy)
-
- val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
- userRepository.setSelectedUserInfo(USER_INFOS[0])
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
assertThat(isCtaDismissed).isFalse()
- clearInvocations(userFileManagerSpy)
+ clearInvocations(userFileManager)
// Received restore finished event.
kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
@@ -141,48 +120,12 @@
runCurrent()
// Get shared preferences from the restored file.
- verify(userFileManagerSpy, atLeastOnce())
- .getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userRepository.getSelectedUserInfo().id
- )
+ verify(userFileManager, atLeastOnce())
+ .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, MAIN_USER.id)
}
- private fun createCommunalPrefsRepositoryImpl(userFileManager: UserFileManager) =
- CommunalPrefsRepositoryImpl(
- testScope.backgroundScope,
- kosmos.testDispatcher,
- userRepository,
- userFileManager,
- kosmos.broadcastDispatcher,
- logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
- tableLogBuffer,
- )
-
- private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
- UserFileManager {
- override fun getFile(fileName: String, userId: Int): File {
- throw UnsupportedOperationException()
- }
-
- override fun getSharedPreferences(
- fileName: String,
- mode: Int,
- userId: Int
- ): SharedPreferences {
- if (fileName != FILE_NAME) {
- throw IllegalArgumentException("Preference files must be $FILE_NAME")
- }
- return sharedPrefs.getValue(userId)
- }
- }
-
companion object {
- val USER_INFOS =
- listOf(
- UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
- UserInfo(/* id= */ 1, "secondary", /* flags= */ 0),
- )
+ val MAIN_USER = UserInfo(0, "main", FLAG_MAIN)
+ val SECONDARY_USER = UserInfo(1, "secondary", 0)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 3d454a2..d951cca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -488,8 +488,16 @@
@Test
fun ctaTile_afterDismiss_doesNotShow() =
testScope.runTest {
+ // Set to main user, so we can dismiss the tile for the main user.
+ val user = userRepository.asMainUser()
+ userTracker.set(
+ userInfos = listOf(user),
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalPrefsRepository.setCtaDismissedForCurrentUser()
+ communalPrefsRepository.setCtaDismissed(user)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
new file mode 100644
index 0000000..7b79d28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_MAIN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalPrefsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest by lazy { kosmos.communalPrefsInteractor }
+
+ @Test
+ fun setCtaDismissed_currentUser() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ assertThat(isCtaDismissed).isFalse()
+ underTest.setCtaDismissed(MAIN_USER)
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ @Test
+ fun setCtaDismissed_anotherUser() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ assertThat(isCtaDismissed).isFalse()
+ underTest.setCtaDismissed(SECONDARY_USER)
+ assertThat(isCtaDismissed).isFalse()
+ }
+
+ @Test
+ fun isCtaDismissed_userSwitch() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ underTest.setCtaDismissed(MAIN_USER)
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ assertThat(isCtaDismissed).isTrue()
+ setSelectedUser(SECONDARY_USER)
+ assertThat(isCtaDismissed).isFalse()
+ }
+
+ @Test
+ fun setDisclaimerDismissed_currentUser() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+
+ assertThat(isDisclaimerDismissed).isFalse()
+ underTest.setDisclaimerDismissed(MAIN_USER)
+ assertThat(isDisclaimerDismissed).isTrue()
+ }
+
+ @Test
+ fun setDisclaimerDismissed_anotherUser() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+
+ assertThat(isDisclaimerDismissed).isFalse()
+ underTest.setDisclaimerDismissed(SECONDARY_USER)
+ assertThat(isDisclaimerDismissed).isFalse()
+ }
+
+ @Test
+ fun isDisclaimerDismissed_userSwitch() =
+ testScope.runTest {
+ setSelectedUser(MAIN_USER)
+ underTest.setDisclaimerDismissed(MAIN_USER)
+ val isDisclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
+
+ assertThat(isDisclaimerDismissed).isTrue()
+ setSelectedUser(SECONDARY_USER)
+ assertThat(isDisclaimerDismissed).isFalse()
+ }
+
+ private suspend fun setSelectedUser(user: UserInfo) {
+ with(kosmos.fakeUserRepository) {
+ setUserInfos(listOf(user))
+ setSelectedUserInfo(user)
+ }
+ kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
+ }
+
+ private companion object {
+ val MAIN_USER = UserInfo(0, "main", FLAG_MAIN)
+ val SECONDARY_USER = UserInfo(1, "secondary", 0)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index d5fe2a1..0190ccb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -48,6 +49,8 @@
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -57,6 +60,7 @@
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -104,10 +108,12 @@
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
communalSceneInteractor = kosmos.communalSceneInteractor
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
kosmos.fakeUserTracker.set(
userInfos = listOf(MAIN_USER_INFO),
selectedUserIndex = 0,
)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
underTest =
@@ -120,6 +126,7 @@
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
kosmos.testDispatcher,
+ kosmos.communalPrefsInteractor,
)
}
@@ -312,6 +319,29 @@
}
}
+ @Test
+ fun showDisclaimer_trueAfterEditModeShowing() =
+ testScope.runTest {
+ val showDisclaimer by collectLastValue(underTest.showDisclaimer)
+
+ assertThat(showDisclaimer).isFalse()
+ underTest.setEditModeState(EditModeState.SHOWING)
+ assertThat(showDisclaimer).isTrue()
+ }
+
+ @Test
+ fun showDisclaimer_falseWhenDismissed() =
+ testScope.runTest {
+ underTest.setEditModeState(EditModeState.SHOWING)
+ kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+
+ val showDisclaimer by collectLastValue(underTest.showDisclaimer)
+
+ assertThat(showDisclaimer).isTrue()
+ underTest.onDisclaimerDismissed()
+ assertThat(showDisclaimer).isFalse()
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index e7a7b15..7a5f81c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -100,7 +100,6 @@
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var user: UserInfo
@Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
@@ -315,6 +314,7 @@
@Test
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
+ setIsMainUser(true)
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
val communalContent by collectLastValue(underTest.communalContent)
@@ -338,6 +338,7 @@
@Test
fun popup_onDismiss_hidesImmediately() =
testScope.runTest {
+ setIsMainUser(true)
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
val currentPopup by collectLastValue(underTest.currentPopup)
@@ -743,13 +744,17 @@
}
private suspend fun setIsMainUser(isMainUser: Boolean) {
- whenever(user.isMain).thenReturn(isMainUser)
- userRepository.setUserInfos(listOf(user))
- userRepository.setSelectedUserInfo(user)
+ val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
+ with(userRepository) {
+ setUserInfos(listOf(user))
+ setSelectedUserInfo(user)
+ }
+ kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
}
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ val SECONDARY_USER_INFO = UserInfo(1, "secondary", 0)
@JvmStatic
@Parameters(name = "{0}")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index d20fec4..5115f5a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -90,7 +90,11 @@
)
reset(transitionRepository)
+ kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null)
kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ assertThat(transitionRepository).noTransitionsStarted()
+
kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true)
runCurrent()
kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 6d9c271..b49e546 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -227,6 +227,15 @@
assertThat(accessibilityDelegateHint)
.isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER)
+ // udfps running
+ setUpState(
+ isUdfpsSupported = true,
+ isUdfpsRunning = true,
+ )
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER)
+
// non-interactive lock icon
fingerprintPropertyRepository.supportsRearFps()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index bc0512a..f43fa50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -30,11 +30,21 @@
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.media.controls.util.SmallHash
+import com.android.systemui.media.controls.util.mediaSmartspaceLogger
+import com.android.systemui.media.controls.util.mockMediaSmartspaceLogger
import com.android.systemui.testKosmos
+import com.android.systemui.util.time.systemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -42,8 +52,20 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger
+ private val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ private val mediaRecommendation =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
- private val underTest: MediaFilterRepository = kosmos.mediaFilterRepository
+ private val underTest: MediaFilterRepository =
+ with(kosmos) {
+ mediaSmartspaceLogger = mockMediaSmartspaceLogger
+ mediaFilterRepository
+ }
@Test
fun addSelectedUserMediaEntry_activeThenInactivate() =
@@ -137,14 +159,6 @@
testScope.runTest {
val smartspaceMediaData by collectLastValue(underTest.smartspaceMediaData)
- val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- val mediaRecommendation =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
underTest.setRecommendation(mediaRecommendation)
assertThat(smartspaceMediaData).isEqualTo(mediaRecommendation)
@@ -164,16 +178,38 @@
val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)
+ underTest.setRecommendation(mediaRecommendation)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
underTest.addSelectedUserMediaEntry(playingData)
underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))
+
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ playingData.smartspaceId,
+ playingData.appUid,
+ cardinality = 2
+ )
+
underTest.addSelectedUserMediaEntry(remoteData)
underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
- assertThat(currentMedia?.size).isEqualTo(2)
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ remoteData.smartspaceId,
+ playingData.appUid,
+ cardinality = 3,
+ rank = 1
+ )
+ assertThat(currentMedia?.size).isEqualTo(3)
assertThat(currentMedia)
.containsExactly(
MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)),
- MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId))
+ MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)),
+ MediaCommonModel.MediaRecommendations(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
)
.inOrder()
}
@@ -222,6 +258,16 @@
underTest.setOrderedMedia()
+ verify(smartspaceLogger, never())
+ .logSmartspaceCardReceived(
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyBoolean(),
+ anyBoolean(),
+ anyInt(),
+ anyInt()
+ )
assertThat(currentMedia?.size).isEqualTo(2)
assertThat(currentMedia)
.containsExactly(
@@ -248,14 +294,6 @@
val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)
- val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- val mediaRecommendations =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))
@@ -271,11 +309,33 @@
underTest.addSelectedUserMediaEntry(playingAndRemoteData)
underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
- underTest.setRecommendation(mediaRecommendations)
+ underTest.setRecommendation(mediaRecommendation)
underTest.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
)
+ underTest.setOrderedMedia()
+ val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ eq(smartspaceId),
+ anyInt(),
+ eq(6),
+ anyBoolean(),
+ anyBoolean(),
+ eq(2),
+ anyInt()
+ )
+ verify(smartspaceLogger, never())
+ .logSmartspaceCardReceived(
+ eq(playingAndLocalData.smartspaceId),
+ anyInt(),
+ anyInt(),
+ anyBoolean(),
+ anyBoolean(),
+ anyInt(),
+ anyInt()
+ )
assertThat(currentMedia?.size).isEqualTo(6)
assertThat(currentMedia)
.containsExactly(
@@ -312,18 +372,10 @@
isPlaying = true,
notificationKey = KEY_2
)
- val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- val mediaRecommendations =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
underTest.setMediaFromRecPackageName(PACKAGE_NAME)
underTest.addSelectedUserMediaEntry(data)
- underTest.setRecommendation(mediaRecommendations)
+ underTest.setRecommendation(mediaRecommendation)
underTest.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
)
@@ -365,6 +417,88 @@
fun hasActiveMedia_noMediaSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }
+ @Test
+ fun updateMediaWithLatency_smartspaceIsLogged() =
+ testScope.runTest {
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val data = createMediaData("app", true, LOCAL, false, instanceId)
+
+ underTest.setRecommendation(mediaRecommendation)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
+
+ val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ eq(smartspaceId),
+ anyInt(),
+ eq(1),
+ eq(true),
+ anyBoolean(),
+ eq(0),
+ anyInt()
+ )
+ reset(smartspaceLogger)
+
+ underTest.addSelectedUserMediaEntry(data)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(data.smartspaceId, data.appUid, cardinality = 2)
+
+ reset(smartspaceLogger)
+
+ underTest.addSelectedUserMediaEntry(data)
+ underTest.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(instanceId, receivedSmartspaceCardLatency = 123)
+ )
+
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()),
+ data.appUid,
+ cardinality = 2,
+ rank = 0,
+ receivedLatencyMillis = 123
+ )
+ }
+
+ @Test
+ fun resumeMedia_loadSmartspace_allSmartspaceIsLogged() =
+ testScope.runTest {
+ val resumeInstanceId = InstanceId.fakeInstanceId(123)
+ val data = createMediaData("app", false, LOCAL, true, resumeInstanceId)
+
+ underTest.addSelectedUserMediaEntry(data.copy(active = false))
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(resumeInstanceId))
+ underTest.setRecommendation(mediaRecommendation)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
+
+ assertThat(underTest.hasActiveMedia()).isFalse()
+ assertThat(underTest.hasAnyMedia()).isTrue()
+ val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ eq(smartspaceId),
+ anyInt(),
+ eq(2),
+ eq(true),
+ anyBoolean(),
+ eq(0),
+ anyInt()
+ )
+ verify(smartspaceLogger)
+ .logSmartspaceCardReceived(
+ SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()),
+ data.appUid,
+ cardinality = 2,
+ rank = 1
+ )
+ }
+
private fun createMediaData(
app: String,
playing: Boolean,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
index 777240c..5826b3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
@@ -17,8 +17,10 @@
package com.android.systemui.volume.panel.component.spatial
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.volume.data.repository.audioRepository
import com.android.systemui.volume.domain.interactor.audioOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
@@ -27,6 +29,8 @@
SpatialAudioComponentInteractor(
audioOutputInteractor,
spatializerInteractor,
+ audioRepository,
+ backgroundCoroutineContext,
testScope.backgroundScope
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index c6c46fa..555d77c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -16,14 +16,22 @@
package com.android.systemui.volume.panel.component.spatial.domain.interactor
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothProfile
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.session.MediaSession
import android.media.session.PlaybackState
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.A2dpProfile
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.HearingAidProfile
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.flags.Flags
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -44,6 +52,7 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,15 +61,29 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SpatialAudioComponentInteractorTest : SysuiTestCase() {
+ @get:Rule val setFlagsRule = SetFlagsRule()
private val kosmos = testKosmos()
private lateinit var underTest: SpatialAudioComponentInteractor
+ private val a2dpProfile: A2dpProfile = mock {
+ whenever(profileId).thenReturn(BluetoothProfile.A2DP)
+ }
+ private val leAudioProfile: LeAudioProfile = mock {
+ whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+ }
+ private val hearingAidProfile: HearingAidProfile = mock {
+ whenever(profileId).thenReturn(BluetoothProfile.HEARING_AID)
+ }
+ private val bluetoothDevice: BluetoothDevice = mock {}
@Before
fun setup() {
with(kosmos) {
val cachedBluetoothDevice: CachedBluetoothDevice = mock {
whenever(address).thenReturn("test_address")
+ whenever(device).thenReturn(bluetoothDevice)
+ whenever(profiles)
+ .thenReturn(listOf(a2dpProfile, leAudioProfile, hearingAidProfile))
}
localMediaRepository.updateCurrentConnectedDevice(
mock<BluetoothMediaDevice> {
@@ -83,7 +106,7 @@
fun setEnabled_changesIsEnabled() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true)
val values by collectValues(underTest.isEnabled)
underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
@@ -106,10 +129,39 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE)
+ fun setEnabled_determinedByBluetoothProfile_a2dpProfileEnabled() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(a2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ whenever(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
+ whenever(hearingAidProfile.isEnabled(bluetoothDevice)).thenReturn(false)
+ spatializerRepository.setIsSpatialAudioAvailable(a2dpAttributes, true)
+ val values by collectValues(underTest.isEnabled)
+
+ underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
+ runCurrent()
+ underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ SpatialAudioEnabledModel.Unknown,
+ SpatialAudioEnabledModel.Disabled,
+ SpatialAudioEnabledModel.SpatialAudioEnabled,
+ )
+ .inOrder()
+ assertThat(spatializerRepository.getSpatialAudioCompatibleDevices())
+ .containsExactly(a2dpAttributes)
+ }
+ }
+ }
+
+ @Test
fun connectedDeviceSupports_isAvailable_SpatialAudio() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsSpatialAudioAvailable(headset, true)
+ spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true)
val isAvailable by collectLastValue(underTest.isAvailable)
@@ -123,8 +175,8 @@
fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsSpatialAudioAvailable(headset, true)
- spatializerRepository.setIsHeadTrackingAvailable(headset, true)
+ spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true)
+ spatializerRepository.setIsHeadTrackingAvailable(bleHeadsetAttributes, true)
val isAvailable by collectLastValue(underTest.isAvailable)
@@ -138,7 +190,7 @@
fun connectedDeviceDoesntSupport_isAvailable_Unavailable() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsSpatialAudioAvailable(headset, false)
+ spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, false)
val isAvailable by collectLastValue(underTest.isAvailable)
@@ -179,7 +231,13 @@
}
private companion object {
- val headset =
+ val a2dpAttributes =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ "test_address"
+ )
+ val bleHeadsetAttributes =
AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_BLE_HEADSET,
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index bcc7bca..a3af9490 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -51,6 +51,15 @@
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toTopOf="parent" />
+ <TextView
+ android:id="@+id/backlinks_data"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/cancel"
+ app:layout_constraintTop_toTopOf="parent" />
+
<ImageView
android:id="@+id/preview"
android:layout_width="0px"
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 84ab0f1..fff1de7 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -51,7 +51,6 @@
android:layout_marginBottom="@dimen/overlay_border_width"
android:layout_gravity="center"
android:elevation="4dp"
- android:contentDescription="@string/screenshot_edit_description"
android:scaleType="fitEnd"
android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
@@ -67,7 +66,6 @@
android:layout_marginBottom="@dimen/overlay_border_width"
android:layout_gravity="center"
android:elevation="4dp"
- android:contentDescription="@string/screenshot_edit_description"
android:scaleType="fitEnd"
android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 82dafc3..abafb01 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -269,6 +269,8 @@
<string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
<!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
<string name="app_clips_save_add_to_note">Add to note</string>
+ <!-- TODO(b/300307759): Temporary string for text view that displays backlinks data. [CHAR LIMIT=NONE] -->
+ <string name="backlinks_string" translatable="false">Open <xliff:g id="appName" example="Google Chrome">%1$s</xliff:g></string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_title">Screen Recorder</string>
@@ -1222,6 +1224,12 @@
<string name="accessibility_action_label_remove_widget">remove widget</string>
<!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_place_widget">place selected widget</string>
+ <!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
+ <string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
+ <!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
+ <string name="communal_widgets_disclaimer_text">To open an app using a widget, you\u2019ll need to verify it\u2019s you. Also, keep in mind that anyone can view them, even when your tablet\u2019s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here.</string>
+ <!-- Button for user to verify they understand the information presented. [CHAR LIMIT=50] -->
+ <string name="communal_widgets_disclaimer_button">Got it</string>
<!-- Related to user switcher --><skip/>
@@ -1380,6 +1388,15 @@
<!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
<string name="no_unseen_notif_text">No new notifications</string>
+ <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=30] -->
+ <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string>
+
+ <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
+ <string name="adaptive_notification_edu_hun_text">Your device now lowers the volume and reduces pop-ups on the screen for up to two minutes when you receive many notifications in a short time span.</string>
+
+ <!-- Action label for going to adaptive notification settings [CHAR LIMIT=20] -->
+ <string name="go_to_adaptive_notification_settings">Turn off</string>
+
<!-- Text which is shown in the locked notification shade when there are currently no notifications, but if the user were to unlock, notifications would appear. [CHAR LIMIT=40] -->
<string name="unlock_to_see_notif_text">Unlock to see older notifications</string>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 68a69d3..37e9dc1a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -42,6 +42,7 @@
import android.view.KeyEvent;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
@@ -178,6 +179,18 @@
private static final int SYSTEM_ACTION_ID_DPAD_CENTER =
AccessibilityService.GLOBAL_ACTION_DPAD_CENTER; // 20
+ /**
+ * Action ID to trigger menu key event.
+ */
+ private static final int SYSTEM_ACTION_ID_MENU =
+ AccessibilityService.GLOBAL_ACTION_MENU; // 21
+
+ /**
+ * Action ID to trigger media play/pause key event.
+ */
+ private static final int SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE =
+ AccessibilityService.GLOBAL_ACTION_MEDIA_PLAY_PAUSE; // 22
+
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final SystemActionsBroadcastReceiver mReceiver;
@@ -307,6 +320,14 @@
R.string.accessibility_system_action_dpad_center_label,
SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER);
+ RemoteAction actionMenu = createRemoteAction(
+ R.string.accessibility_system_action_menu_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_MENU);
+
+ RemoteAction actionMediaPlayPause = createRemoteAction(
+ R.string.accessibility_system_action_media_play_pause_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_MEDIA_PLAY_PAUSE);
+
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
@@ -326,6 +347,8 @@
mA11yManager.registerSystemAction(actionDpadLeft, SYSTEM_ACTION_ID_DPAD_LEFT);
mA11yManager.registerSystemAction(actionDpadRight, SYSTEM_ACTION_ID_DPAD_RIGHT);
mA11yManager.registerSystemAction(actionDpadCenter, SYSTEM_ACTION_ID_DPAD_CENTER);
+ mA11yManager.registerSystemAction(actionMenu, SYSTEM_ACTION_ID_MENU);
+ mA11yManager.registerSystemAction(actionMediaPlayPause, SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE);
registerOrUnregisterDismissNotificationShadeAction();
}
@@ -435,6 +458,14 @@
labelId = R.string.accessibility_system_action_dpad_center_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_DPAD_CENTER;
break;
+ case SYSTEM_ACTION_ID_MENU:
+ labelId = R.string.accessibility_system_action_menu_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_MENU;
+ break;
+ case SYSTEM_ACTION_ID_MEDIA_PLAY_PAUSE:
+ labelId = R.string.accessibility_system_action_media_play_pause_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_MEDIA_PLAY_PAUSE;
+ break;
default:
return;
}
@@ -570,6 +601,16 @@
sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
}
+ @VisibleForTesting
+ void handleMenu() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MENU);
+ }
+
+ @VisibleForTesting
+ void handleMediaPlayPause() {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -593,6 +634,9 @@
private static final String INTENT_ACTION_DPAD_LEFT = "SYSTEM_ACTION_DPAD_LEFT";
private static final String INTENT_ACTION_DPAD_RIGHT = "SYSTEM_ACTION_DPAD_RIGHT";
private static final String INTENT_ACTION_DPAD_CENTER = "SYSTEM_ACTION_DPAD_CENTER";
+ private static final String INTENT_ACTION_MENU = "SYSTEM_ACTION_MENU";
+ private static final String INTENT_ACTION_MEDIA_PLAY_PAUSE =
+ "SYSTEM_ACTION_MEDIA_PLAY_PAUSE";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -613,7 +657,9 @@
case INTENT_ACTION_DPAD_DOWN:
case INTENT_ACTION_DPAD_LEFT:
case INTENT_ACTION_DPAD_RIGHT:
- case INTENT_ACTION_DPAD_CENTER: {
+ case INTENT_ACTION_DPAD_CENTER:
+ case INTENT_ACTION_MENU:
+ case INTENT_ACTION_MEDIA_PLAY_PAUSE: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -646,6 +692,8 @@
intentFilter.addAction(INTENT_ACTION_DPAD_LEFT);
intentFilter.addAction(INTENT_ACTION_DPAD_RIGHT);
intentFilter.addAction(INTENT_ACTION_DPAD_CENTER);
+ intentFilter.addAction(INTENT_ACTION_MENU);
+ intentFilter.addAction(INTENT_ACTION_MEDIA_PLAY_PAUSE);
return intentFilter;
}
@@ -725,6 +773,18 @@
handleDpadCenter();
break;
}
+ case INTENT_ACTION_MENU: {
+ if (Flags.globalActionMenu()) {
+ handleMenu();
+ }
+ break;
+ }
+ case INTENT_ACTION_MEDIA_PLAY_PAUSE: {
+ if (Flags.globalActionMediaPlayPause()) {
+ handleMediaPlayPause();
+ }
+ break;
+ }
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
index 09bf04c..9cb26f3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
@@ -20,9 +20,10 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.util.time.SystemClock;
-import java.util.ArrayList;
+import com.google.common.collect.Sets;
+
import java.util.Collection;
-import java.util.List;
+import java.util.Set;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
@@ -52,7 +53,7 @@
private final SystemClock mSystemClock;
DelayQueue<CombinedResult> mResults = new DelayQueue<>();
- private final List<BeliefListener> mBeliefListeners = new ArrayList<>();
+ private final Set<BeliefListener> mBeliefListeners = Sets.newConcurrentHashSet();
@Inject
HistoryTracker(SystemClock systemClock) {
@@ -161,11 +162,15 @@
}
void addBeliefListener(BeliefListener listener) {
- mBeliefListeners.add(listener);
+ if (listener != null) {
+ mBeliefListeners.add(listener);
+ }
}
void removeBeliefListener(BeliefListener listener) {
- mBeliefListeners.remove(listener);
+ if (listener != null) {
+ mBeliefListeners.remove(listener);
+ }
}
/**
* Represents a falsing score combing all the classifiers together.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index b27fcfc..d8067b8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -27,26 +27,17 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.log.dagger.CommunalTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
-import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/**
@@ -56,10 +47,16 @@
interface CommunalPrefsRepository {
/** Whether the CTA tile has been dismissed. */
- val isCtaDismissed: Flow<Boolean>
+ fun isCtaDismissed(user: UserInfo): Flow<Boolean>
+
+ /** Whether the lock screen widget disclaimer has been dismissed by the user. */
+ fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean>
/** Save the CTA tile dismissed state for the current user. */
- suspend fun setCtaDismissedForCurrentUser()
+ suspend fun setCtaDismissed(user: UserInfo)
+
+ /** Save the lock screen widget disclaimer dismissed state for the current user. */
+ suspend fun setDisclaimerDismissed(user: UserInfo)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -67,75 +64,43 @@
class CommunalPrefsRepositoryImpl
@Inject
constructor(
- @Background private val backgroundScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
broadcastDispatcher: BroadcastDispatcher,
@CommunalLog logBuffer: LogBuffer,
- @CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
+ private val logger by lazy { Logger(logBuffer, TAG) }
- private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+ override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
+ readKeyForUser(user, CTA_DISMISSED_STATE)
+
+ override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
+ readKeyForUser(user, DISCLAIMER_DISMISSED_STATE)
/**
- * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
- * initial value.
+ * Emits an event each time a Backup & Restore restoration job is completed, and once at the
+ * start of collection.
*/
private val backupRestorationEvents: Flow<Unit> =
- broadcastDispatcher.broadcastFlow(
- filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
- flags = Context.RECEIVER_NOT_EXPORTED,
- permission = BackupHelper.PERMISSION_SELF,
- )
-
- override val isCtaDismissed: Flow<Boolean> =
- combine(
- userRepository.selectedUserInfo,
- // Make sure combine can emit even if we never get a Backup & Restore event,
- // which is the most common case as restoration only happens on initial device
- // setup.
- backupRestorationEvents.emitOnStart().onEach {
- logger.i("Restored state for communal preferences.")
- },
- ) { user, _ ->
- user
- }
- .flatMapLatest(::observeCtaDismissState)
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnPrefix = "",
- columnName = "isCtaDismissed",
- initialValue = false,
+ broadcastDispatcher
+ .broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
)
- .stateIn(
- scope = backgroundScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
+ .onEach { logger.i("Restored state for communal preferences.") }
+ .emitOnStart()
- override suspend fun setCtaDismissedForCurrentUser() =
+ override suspend fun setCtaDismissed(user: UserInfo) =
withContext(bgDispatcher) {
- getSharedPrefsForUser(userRepository.getSelectedUserInfo())
- .edit()
- .putBoolean(CTA_DISMISSED_STATE, true)
- .apply()
-
+ getSharedPrefsForUser(user).edit().putBoolean(CTA_DISMISSED_STATE, true).apply()
logger.i("Dismissed CTA tile")
}
- private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
- getSharedPrefsForUser(user)
- .observe()
- // Emit at the start of collection to ensure we get an initial value
- .onStart { emit(Unit) }
- .map { getCtaDismissedState() }
- .flowOn(bgDispatcher)
-
- private suspend fun getCtaDismissedState(): Boolean =
+ override suspend fun setDisclaimerDismissed(user: UserInfo) =
withContext(bgDispatcher) {
- getSharedPrefsForUser(userRepository.getSelectedUserInfo())
- .getBoolean(CTA_DISMISSED_STATE, false)
+ getSharedPrefsForUser(user).edit().putBoolean(DISCLAIMER_DISMISSED_STATE, true).apply()
+ logger.i("Dismissed widget disclaimer")
}
private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
@@ -146,9 +111,19 @@
)
}
+ private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> {
+ return backupRestorationEvents
+ .flatMapLatest {
+ val sharedPrefs = getSharedPrefsForUser(user)
+ sharedPrefs.observe().emitOnStart().map { sharedPrefs.getBoolean(key, false) }
+ }
+ .flowOn(bgDispatcher)
+ }
+
companion object {
- const val TAG = "CommunalRepository"
+ const val TAG = "CommunalPrefsRepository"
const val FILE_NAME = "communal_hub_prefs"
const val CTA_DISMISSED_STATE = "cta_dismissed"
+ const val DISCLAIMER_DISMISSED_STATE = "disclaimer_dismissed"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 00678a8..9f3ade9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -28,7 +28,6 @@
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
-import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
@@ -99,7 +98,7 @@
@Background val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
private val widgetRepository: CommunalWidgetRepository,
- private val communalPrefsRepository: CommunalPrefsRepository,
+ private val communalPrefsInteractor: CommunalPrefsInteractor,
private val mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
@@ -325,7 +324,7 @@
}
/** Dismiss the CTA tile from the hub in view mode. */
- suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
+ suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed()
/** Add a widget at the specified position. */
fun addWidget(
@@ -461,7 +460,7 @@
/** CTA tile to be displayed in the glanceable hub (view mode). */
val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
- communalPrefsRepository.isCtaDismissed.map { isDismissed ->
+ communalPrefsInteractor.isCtaDismissed.map { isDismissed ->
if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
new file mode 100644
index 0000000..3517650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.communal.domain.interactor
+
+import android.content.pm.UserInfo
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.communal.data.repository.CommunalPrefsRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalPrefsInteractor
+@Inject
+constructor(
+ @Background private val bgScope: CoroutineScope,
+ private val repository: CommunalPrefsRepository,
+ userInteractor: SelectedUserInteractor,
+ private val userTracker: UserTracker,
+ @CommunalTableLog tableLogBuffer: TableLogBuffer
+) {
+
+ val isCtaDismissed: Flow<Boolean> =
+ userInteractor.selectedUserInfo
+ .flatMapLatest { user -> repository.isCtaDismissed(user) }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isCtaDismissed",
+ initialValue = false,
+ )
+ .stateIn(
+ scope = bgScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ suspend fun setCtaDismissed(user: UserInfo = userTracker.userInfo) =
+ repository.setCtaDismissed(user)
+
+ val isDisclaimerDismissed: Flow<Boolean> =
+ userInteractor.selectedUserInfo
+ .flatMapLatest { user -> repository.isDisclaimerDismissed(user) }
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ columnPrefix = "",
+ columnName = "isDisclaimerDismissed",
+ initialValue = false,
+ )
+ .stateIn(
+ scope = bgScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ fun setDisclaimerDismissed(user: UserInfo = userTracker.userInfo) {
+ bgScope.launch("$TAG#setDisclaimerDismissed") { repository.setDisclaimerDismissed(user) }
+ }
+
+ private companion object {
+ const val TAG = "CommunalPrefsInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c0c5861..9185384 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -26,6 +26,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags.enableWidgetPickerSizeFilter
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -42,6 +43,7 @@
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineDispatcher
@@ -67,6 +69,7 @@
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val communalPrefsInteractor: CommunalPrefsInteractor,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -76,9 +79,16 @@
override val isCommunalContentVisible: Flow<Boolean> =
communalSceneInteractor.editModeState.map { it == EditModeState.SHOWING }
+ val showDisclaimer: Flow<Boolean> =
+ allOf(isCommunalContentVisible, not(communalPrefsInteractor.isDisclaimerDismissed))
+
+ fun onDisclaimerDismissed() {
+ communalPrefsInteractor.setDisclaimerDismissed()
+ }
+
/**
- * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE]
- * and edit mode is open.
+ * Emits when edit mode activity can show, after we've transitioned to [KeyguardState.GONE] and
+ * edit mode is open.
*/
val canShowEditMode =
allOf(
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 73b7a8a..e4b290d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -85,7 +85,7 @@
class ConnectedDisplayInteractorImpl
@Inject
constructor(
- private val virtualDeviceManager: VirtualDeviceManager,
+ private val virtualDeviceManager: VirtualDeviceManager?,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
deviceStateRepository: DeviceStateRepository,
@@ -156,6 +156,7 @@
private fun isVirtualDeviceOwnedMirrorDisplay(display: Display): Boolean {
return Flags.interactiveScreenMirror() &&
+ virtualDeviceManager != null &&
virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(display.displayId)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
new file mode 100644
index 0000000..34b10c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.content.res.Resources
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
+import android.view.KeyEvent.KEYCODE_DPAD_UP
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class MultitaskingShortcutsSource @Inject constructor(private val resources: Resources) {
+
+ fun splitScreenShortcuts() =
+ listOf(
+ // Enter Split screen with current app to RHS:
+ // - Meta + Ctrl + Right arrow
+ shortcut(resources.getString(R.string.system_multitasking_rhs)) {
+ command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_RIGHT)
+ },
+ // Enter Split screen with current app to LHS:
+ // - Meta + Ctrl + Left arrow
+ shortcut(resources.getString(R.string.system_multitasking_lhs)) {
+ command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_LEFT)
+ },
+ // Switch from Split screen to full screen:
+ // - Meta + Ctrl + Up arrow
+ shortcut(resources.getString(R.string.system_multitasking_full_screen)) {
+ command(META_META_ON, META_CTRL_ON, KEYCODE_DPAD_UP)
+ },
+ // Change split screen focus to RHS:
+ // - Meta + Alt + Right arrow
+ shortcut(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) {
+ command(META_META_ON, META_ALT_ON, KEYCODE_DPAD_RIGHT)
+ },
+ // Change split screen focus to LHS:
+ // - Meta + Alt + Left arrow
+ shortcut(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) {
+ command(META_META_ON, META_ALT_ON, KEYCODE_DPAD_LEFT)
+ },
+ )
+
+ fun recentsShortcuts() =
+ listOf(
+ // Cycle through recent apps (forward):
+ // - Alt + Tab
+ shortcut(resources.getString(R.string.group_system_cycle_forward)) {
+ command(META_ALT_ON, KEYCODE_TAB)
+ },
+ // Cycle through recent apps (back):
+ // - Shift + Alt + Tab
+ shortcut(resources.getString(R.string.group_system_cycle_back)) {
+ command(META_SHIFT_ON, META_ALT_ON, KEYCODE_TAB)
+ },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
new file mode 100644
index 0000000..a4304e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.keyboard.shortcut.data.source
+
+import android.content.res.Resources
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.KEYCODE_DEL
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_ENTER
+import android.view.KeyEvent.KEYCODE_ESCAPE
+import android.view.KeyEvent.KEYCODE_H
+import android.view.KeyEvent.KEYCODE_I
+import android.view.KeyEvent.KEYCODE_L
+import android.view.KeyEvent.KEYCODE_N
+import android.view.KeyEvent.KEYCODE_S
+import android.view.KeyEvent.KEYCODE_SLASH
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_META_ON
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class SystemShortcutsSource @Inject constructor(private val resources: Resources) {
+
+ fun generalShortcuts() =
+ listOf(
+ // Access list of all apps and search (i.e. Search/Launcher):
+ // - Meta
+ shortcut(resources.getString(R.string.group_system_access_all_apps_search)) {
+ command(META_META_ON)
+ },
+ // Access home screen:
+ // - Meta + H
+ // - Meta + Enter
+ shortcut(resources.getString(R.string.group_system_access_home_screen)) {
+ command(META_META_ON, KEYCODE_H)
+ command(META_META_ON, KEYCODE_ENTER)
+ },
+ // Overview of open apps:
+ // - Meta + Tab
+ shortcut(resources.getString(R.string.group_system_overview_open_apps)) {
+ command(META_META_ON, KEYCODE_TAB)
+ },
+ // Back: go back to previous state (back button)
+ // - Meta + Escape OR
+ // - Meta + Backspace OR
+ // - Meta + Left arrow
+ shortcut(resources.getString(R.string.group_system_go_back)) {
+ command(META_META_ON, KEYCODE_ESCAPE)
+ command(META_META_ON, KEYCODE_DEL)
+ command(META_META_ON, KEYCODE_DPAD_LEFT)
+ },
+ // Take a full screenshot:
+ // - Meta + Ctrl + S
+ shortcut(resources.getString(R.string.group_system_full_screenshot)) {
+ command(META_META_ON, META_CTRL_ON, KEYCODE_S)
+ },
+ // Access list of system / apps shortcuts:
+ // - Meta + /
+ shortcut(resources.getString(R.string.group_system_access_system_app_shortcuts)) {
+ command(META_META_ON, KEYCODE_SLASH)
+ },
+ // Access notification shade:
+ // - Meta + N
+ shortcut(resources.getString(R.string.group_system_access_notification_shade)) {
+ command(META_META_ON, KEYCODE_N)
+ },
+ // Lock screen:
+ // - Meta + L
+ shortcut(resources.getString(R.string.group_system_lock_screen)) {
+ command(META_META_ON, KEYCODE_L)
+ },
+ )
+
+ fun systemAppsShortcuts() =
+ listOf(
+ // Pull up Notes app for quick memo:
+ // - Meta + Ctrl + N
+ shortcut(resources.getString(R.string.group_system_quick_memo)) {
+ command(META_META_ON, META_CTRL_ON, KEYCODE_N)
+ },
+ // Access system settings:
+ // - Meta + I
+ shortcut(resources.getString(R.string.group_system_access_system_settings)) {
+ command(META_META_ON, KEYCODE_I)
+ },
+ // Access Assistant:
+ // - Meta + A
+ shortcut(resources.getString(R.string.group_system_access_google_assistant)) {
+ command(META_META_ON, KEYCODE_A)
+ },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
new file mode 100644
index 0000000..ea90b18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.keyboard.shortcut.shared.model
+
+import android.graphics.drawable.Icon
+
+data class Shortcut(val label: String, val icon: Icon? = null, val commands: List<ShortcutCommand>)
+
+class ShortcutBuilder(private val label: String, private val icon: Icon? = null) {
+ val commands = mutableListOf<ShortcutCommand>()
+
+ fun command(vararg keyCodes: Int) {
+ commands += ShortcutCommand(keyCodes.toList())
+ }
+
+ fun build() = Shortcut(label, icon, commands)
+}
+
+fun shortcut(label: String, icon: Icon? = null, block: ShortcutBuilder.() -> Unit): Shortcut =
+ ShortcutBuilder(label).apply(block).build()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
new file mode 100644
index 0000000..747efba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.keyboard.shortcut.shared.model
+
+class ShortcutCommand(val keyCodes: List<Int>)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 49d00af..5573f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -40,6 +40,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
@@ -168,7 +169,9 @@
keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit
keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded ->
if (keyguardOccluded) {
- primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled
+ primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled.drop(
+ 1
+ ) // drop the initial state
} else {
emptyFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index f8063c9..db33acb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -115,13 +115,28 @@
}
private fun removeViewFromWindowManager() {
- if (alternateBouncerView == null || !alternateBouncerView!!.isAttachedToWindow) {
- return
- }
+ alternateBouncerView?.let {
+ alternateBouncerView = null
+ if (it.isAttachedToWindow) {
+ it.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler)
+ Log.d(TAG, "Removing alternate bouncer view immediately")
+ windowManager.get().removeView(it)
+ } else {
+ // once the view is attached, remove it
+ it.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(view: View) {
+ it.removeOnAttachStateChangeListener(this)
+ it.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler)
+ Log.d(TAG, "Removing alternate bouncer view on attached")
+ windowManager.get().removeView(it)
+ }
- windowManager.get().removeView(alternateBouncerView)
- alternateBouncerView!!.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler)
- alternateBouncerView = null
+ override fun onViewDetachedFromWindow(view: View) {}
+ }
+ )
+ }
+ }
}
private val onAttachAddBackGestureHandler =
@@ -151,7 +166,7 @@
}
private fun addViewToWindowManager() {
- if (alternateBouncerView?.isAttachedToWindow == true) {
+ if (alternateBouncerView != null) {
return
}
@@ -159,6 +174,7 @@
layoutInflater.get().inflate(R.layout.alternate_bouncer, null, false)
as ConstraintLayout
+ Log.d(TAG, "Adding alternate bouncer view")
windowManager.get().addView(alternateBouncerView, layoutParams)
alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 23c2491..3e4253b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.util.kotlin.DisposableHandles
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,7 +54,15 @@
viewModel: KeyguardIndicationAreaViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
- indicationController.setIndicationArea(view)
+ val disposables = DisposableHandles()
+
+ // As the indication controller is a singleton, reset the view back to the previous view
+ // once the current view is disposed.
+ val previous = indicationController.indicationArea
+ indicationController.indicationArea = view
+ disposables += DisposableHandle {
+ previous?.let { indicationController.indicationArea = it }
+ }
val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
val indicationTextBottom: TextView =
@@ -63,7 +72,7 @@
view.clipToPadding = false
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
- val disposableHandle =
+ disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch("$TAG#viewModel.alpha") {
@@ -126,7 +135,7 @@
}
}
}
- return disposableHandle
+ return disposables
}
private fun loadFromResources(view: View): ConfigurationBasedDimensions {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 4688088..5ce1b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -284,9 +284,9 @@
private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
DeviceEntryIconView.AccessibilityHintType {
return when (this) {
+ DeviceEntryIconView.IconType.FINGERPRINT,
DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
- DeviceEntryIconView.IconType.FINGERPRINT,
DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 6a91d1b..a2d7fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -26,6 +26,8 @@
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.media.controls.util.MediaSmartspaceLogger
+import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.time.SystemClock
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -43,9 +45,10 @@
class MediaFilterRepository
@Inject
constructor(
- @Application applicationContext: Context,
+ @Application private val applicationContext: Context,
private val systemClock: SystemClock,
private val configurationController: ConfigurationController,
+ private val smartspaceLogger: MediaSmartspaceLogger,
) {
val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
@@ -211,6 +214,12 @@
isMediaFromRec(it)
)
sortedMap[sortKey] = newCommonModel
+ val isUpdate =
+ sortedMedia.values.any { commonModel ->
+ commonModel is MediaCommonModel.MediaControl &&
+ commonModel.mediaLoadedModel.instanceId ==
+ mediaDataLoadingModel.instanceId
+ }
// On Addition or tapping on recommendations, we should show the new order of media.
if (mediaFromRecPackageName == it.packageName) {
@@ -218,30 +227,50 @@
mediaFromRecPackageName = null
_currentMedia.value = sortedMap.values.toList()
}
- } else if (sortedMap.size > _currentMedia.value.size && it.active) {
- _currentMedia.value = sortedMap.values.toList()
} else {
- // When loading an update for an existing media control.
+ var isNewToCurrentMedia = true
val currentList =
mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) }
currentList.forEachIndexed { index, mediaCommonModel ->
if (
mediaCommonModel is MediaCommonModel.MediaControl &&
mediaCommonModel.mediaLoadedModel.instanceId ==
- mediaDataLoadingModel.instanceId &&
- mediaCommonModel != newCommonModel
+ mediaDataLoadingModel.instanceId
) {
- // Update media model if changed.
- currentList[index] = newCommonModel
+ // When loading an update for an existing media control.
+ isNewToCurrentMedia = false
+ if (mediaCommonModel != newCommonModel) {
+ // Update media model if changed.
+ currentList[index] = newCommonModel
+ }
}
}
- _currentMedia.value = currentList
+ if (isNewToCurrentMedia && it.active) {
+ _currentMedia.value = sortedMap.values.toList()
+ } else {
+ _currentMedia.value = currentList
+ }
+ }
+
+ sortedMedia = sortedMap
+
+ if (!isUpdate) {
+ val rank = sortedMedia.values.indexOf(newCommonModel)
+ if (isSmartspaceLoggingEnabled(newCommonModel, rank)) {
+ smartspaceLogger.logSmartspaceCardReceived(
+ it.smartspaceId,
+ it.appUid,
+ cardinality = _currentMedia.value.size,
+ isSsReactivated = mediaDataLoadingModel.isSsReactivated,
+ rank = rank,
+ )
+ }
+ } else if (mediaDataLoadingModel.receivedSmartspaceCardLatency != 0) {
+ logSmartspaceAllMediaCards(mediaDataLoadingModel.receivedSmartspaceCardLatency)
}
}
}
- sortedMedia = sortedMap
-
// On removal we want to keep the order being shown to user.
if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) {
_currentMedia.value =
@@ -249,6 +278,7 @@
commonModel !is MediaCommonModel.MediaControl ||
mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId
}
+ sortedMedia = sortedMap
}
}
@@ -271,21 +301,45 @@
isPlaying = false,
active = _smartspaceMediaData.value.isActive,
)
+ val newCommonModel = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
when (smartspaceMediaLoadingModel) {
- is SmartspaceMediaLoadingModel.Loaded ->
- sortedMap[sortKey] =
- MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
- is SmartspaceMediaLoadingModel.Removed ->
+ is SmartspaceMediaLoadingModel.Loaded -> {
+ sortedMap[sortKey] = newCommonModel
+ _currentMedia.value = sortedMap.values.toList()
+ sortedMedia = sortedMap
+
+ if (isRecommendationActive()) {
+ val hasActivatedExistedResumeMedia =
+ !hasActiveMedia() &&
+ hasAnyMedia() &&
+ smartspaceMediaLoadingModel.isPrioritized
+ if (hasActivatedExistedResumeMedia) {
+ // Log resume card received if resumable media card is reactivated and
+ // recommendation card is valid and ranked first
+ logSmartspaceAllMediaCards(
+ (systemClock.currentTimeMillis() -
+ _smartspaceMediaData.value.headphoneConnectionTimeMillis)
+ .toInt()
+ )
+ }
+
+ smartspaceLogger.logSmartspaceCardReceived(
+ SmallHash.hash(_smartspaceMediaData.value.targetId),
+ _smartspaceMediaData.value.getUid(applicationContext),
+ cardinality = _currentMedia.value.size,
+ isRecommendationCard = true,
+ rank = _currentMedia.value.indexOf(newCommonModel),
+ )
+ }
+ }
+ is SmartspaceMediaLoadingModel.Removed -> {
_currentMedia.value =
_currentMedia.value.filter { commonModel ->
commonModel !is MediaCommonModel.MediaRecommendations
}
+ sortedMedia = sortedMap
+ }
}
-
- if (sortedMap.size > sortedMedia.size) {
- _currentMedia.value = sortedMap.values.toList()
- }
- sortedMedia = sortedMap
}
fun setOrderedMedia() {
@@ -315,4 +369,35 @@
private fun isMediaFromRec(data: MediaData): Boolean {
return data.isPlaying == true && mediaFromRecPackageName == data.packageName
}
+
+ /** Log all media cards if smartspace logging is enabled for each. */
+ private fun logSmartspaceAllMediaCards(receivedSmartspaceCardLatency: Int) {
+ sortedMedia.values.forEachIndexed { index, mediaCommonModel ->
+ if (mediaCommonModel is MediaCommonModel.MediaControl) {
+ _selectedUserEntries.value[mediaCommonModel.mediaLoadedModel.instanceId]?.let {
+ it.smartspaceId =
+ SmallHash.hash(it.appUid + systemClock.currentTimeMillis().toInt())
+ it.isImpressed = false
+
+ if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) {
+ smartspaceLogger.logSmartspaceCardReceived(
+ it.smartspaceId,
+ it.appUid,
+ cardinality = _currentMedia.value.size,
+ isSsReactivated = mediaCommonModel.mediaLoadedModel.isSsReactivated,
+ rank = index,
+ receivedLatencyMillis = receivedSmartspaceCardLatency,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun isSmartspaceLoggingEnabled(commonModel: MediaCommonModel, index: Int): Boolean {
+ return sortedMedia.size > index &&
+ (_smartspaceMediaData.value.expiryTimeMs != 0L ||
+ isRecommendationActive() ||
+ commonModel is MediaCommonModel.MediaRecommendations)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index f78a0f9..31bd4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -180,7 +180,13 @@
mediaData.instanceId
)
mediaFilterRepository.addMediaDataLoadingState(
- MediaDataLoadingModel.Loaded(lastActiveId)
+ MediaDataLoadingModel.Loaded(
+ lastActiveId,
+ receivedSmartspaceCardLatency =
+ (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
+ .toInt(),
+ isSsReactivated = true
+ )
)
mediaLoadingLogger.logMediaLoaded(
mediaData.instanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 37dffd1..adcfba7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -86,6 +86,7 @@
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
@@ -721,6 +722,7 @@
appUid = appUid,
isExplicit = isExplicit,
resumeProgress = progress,
+ smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
)
)
}
@@ -902,6 +904,7 @@
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
+ smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
index 11a5629..40b3477 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
@@ -99,6 +99,12 @@
/** Track progress (0 - 1) to display for players where [resumption] is true */
val resumeProgress: Double? = null,
+
+ /** Smartspace Id, used for logging. */
+ var smartspaceId: Int = -1,
+
+ /** If media card was visible to user, used for logging. */
+ var isImpressed: Boolean = false,
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
index 170f1f7..c8a02fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
@@ -27,6 +27,8 @@
data class Loaded(
override val instanceId: InstanceId,
val immediatelyUpdateUi: Boolean = true,
+ val receivedSmartspaceCardLatency: Int = 0,
+ val isSsReactivated: Boolean = false,
) : MediaDataLoadingModel()
/** Media data has been removed. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
index 9e15dbb..96c3fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
@@ -48,6 +48,8 @@
val instanceId: InstanceId? = null,
/** The timestamp in milliseconds indicating when the card should be removed */
val expiryTimeMs: Long = 0L,
+ /** If recommendation card was visible to user, used for logging. */
+ var isImpressed: Boolean = false,
) {
/**
* Indicates if all the data is valid.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt
new file mode 100644
index 0000000..01fbf4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.media.controls.util
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+import javax.inject.Inject
+
+/** Logger class for Smartspace logging events. */
+@SysUISingleton
+class MediaSmartspaceLogger @Inject constructor() {
+ /**
+ * Log Smartspace card received event
+ *
+ * @param instanceId id to uniquely identify a card.
+ * @param uid uid for the application that media comes from.
+ * @param cardinality number of card in carousel.
+ * @param isRecommendationCard whether media card being logged is a recommendations card.
+ * @param isSsReactivated indicates resume media card is reactivated by Smartspace
+ * recommendation signal
+ * @param rank the rank for media card in the media carousel, starting from 0
+ * @param receivedLatencyMillis latency in milliseconds for card received events.
+ */
+ fun logSmartspaceCardReceived(
+ instanceId: Int,
+ uid: Int,
+ cardinality: Int,
+ isRecommendationCard: Boolean = false,
+ isSsReactivated: Boolean = false,
+ rank: Int = 0,
+ receivedLatencyMillis: Int = 0,
+ ) {
+ logSmartspaceCardReported(
+ SMARTSPACE_CARD_RECEIVED_EVENT,
+ instanceId,
+ uid,
+ surfaces =
+ intArrayOf(
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY,
+ ),
+ cardinality,
+ isRecommendationCard,
+ isSsReactivated,
+ rank = rank,
+ receivedLatencyMillis = receivedLatencyMillis,
+ )
+ }
+
+ /**
+ * Log Smartspace card UI event
+ *
+ * @param eventId id of the event. eg: dismiss, click, or seen.
+ * @param instanceId id to uniquely identify a card.
+ * @param uid uid for the application that media comes from.
+ * @param location location of media carousel holding media card.
+ * @param cardinality number of card in carousel.
+ * @param isRecommendationCard whether media card being logged is a recommendations card.
+ * @param isSsReactivated indicates resume media card is reactivated by Smartspace
+ * recommendation signal
+ * @param rank the rank for media card in the media carousel, starting from 0
+ * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
+ */
+ fun logSmartspaceCardUIEvent(
+ eventId: Int,
+ instanceId: Int,
+ uid: Int,
+ location: Int,
+ cardinality: Int,
+ isRecommendationCard: Boolean = false,
+ isSsReactivated: Boolean = false,
+ rank: Int = 0,
+ isSwipeToDismiss: Boolean = false,
+ ) {
+ logSmartspaceCardReported(
+ eventId,
+ instanceId,
+ uid,
+ surfaces = intArrayOf(location),
+ cardinality,
+ isRecommendationCard,
+ isSsReactivated,
+ rank = rank,
+ isSwipeToDismiss = isSwipeToDismiss,
+ )
+ }
+
+ /**
+ * Log Smartspace events
+ *
+ * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+ * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+ * instanceId
+ * @param uid uid for the application that media comes from
+ * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
+ * the event happened
+ * @param cardinality number of card in carousel.
+ * @param isRecommendationCard whether media card being logged is a recommendations card.
+ * @param isSsReactivated indicates resume media card is reactivated by Smartspace
+ * recommendation signal
+ * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+ * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+ * @param interactedSubcardCardinality how many media items were shown to the user when there is
+ * user interaction
+ * @param rank the rank for media card in the media carousel, starting from 0
+ * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
+ * between headphone connection to sysUI displays media recommendation card
+ * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
+ */
+ private fun logSmartspaceCardReported(
+ eventId: Int,
+ instanceId: Int,
+ uid: Int,
+ surfaces: IntArray,
+ cardinality: Int,
+ isRecommendationCard: Boolean,
+ isSsReactivated: Boolean,
+ interactedSubcardRank: Int = 0,
+ interactedSubcardCardinality: Int = 0,
+ rank: Int = 0,
+ receivedLatencyMillis: Int = 0,
+ isSwipeToDismiss: Boolean = false,
+ ) {
+ surfaces.forEach { surface ->
+ SysUiStatsLog.write(
+ SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+ eventId,
+ instanceId,
+ // Deprecated, replaced with AiAi feature type so we don't need to create logging
+ // card type for each new feature.
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
+ surface,
+ // Use -1 as rank value to indicate user swipe to dismiss the card
+ if (isSwipeToDismiss) -1 else rank,
+ cardinality,
+ if (isRecommendationCard) {
+ 15 // MEDIA_RECOMMENDATION
+ } else if (isSsReactivated) {
+ 43 // MEDIA_RESUME_SS_ACTIVATED
+ } else {
+ 31 // MEDIA_RESUME
+ },
+ uid,
+ interactedSubcardRank,
+ interactedSubcardCardinality,
+ receivedLatencyMillis,
+ null, // Media cards cannot have subcards.
+ null // Media cards don't have dimensions today.
+ )
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Log Smartspace card event id: $eventId instance id: $instanceId" +
+ " surface: $surface rank: $rank cardinality: $cardinality " +
+ "isRecommendationCard: $isRecommendationCard " +
+ "isSsReactivated: $isSsReactivated" +
+ "uid: $uid " +
+ "interactedSubcardRank: $interactedSubcardRank " +
+ "interactedSubcardCardinality: $interactedSubcardCardinality " +
+ "received_latency_millis: $receivedLatencyMillis"
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "MediaSmartspaceLogger"
+ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+ private const val SMARTSPACE_CARD_RECEIVED_EVENT = 759
+ const val SMARTSPACE_CARD_CLICK_EVENT = 760
+ const val SMARTSPACE_CARD_DISMISS_EVENT = 761
+ const val SMARTSPACE_CARD_SEEN_EVENT = 800
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index ee816942..47e0691 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -98,7 +98,7 @@
createAndShow(
packageName = null,
aboveStatusBar = false,
- dialogTransitionAnimatorController = null,
+ dialogTransitionAnimatorController = controller,
includePlaybackAndAppMetadata = false,
userHandle = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt
index 2ffb783..5f16886 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt
@@ -18,6 +18,7 @@
import android.app.assist.AssistContent
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
+import com.android.systemui.screenshot.ui.viewmodel.PreviewAction
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -84,9 +85,9 @@
}
inner class ActionsCallback(private val screenshotId: UUID) {
- fun providePreviewAction(onClick: () -> Unit) {
+ fun providePreviewAction(previewAction: PreviewAction) {
if (screenshotId == currentScreenshotId) {
- viewModel.setPreviewAction(onClick)
+ viewModel.setPreviewAction(previewAction)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index b8029c8..c216f1d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -29,6 +29,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 dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -40,7 +41,9 @@
*/
interface ScreenshotActionsProvider {
fun onScrollChipReady(onClick: Runnable)
+
fun onScrollChipInvalidated()
+
fun setCompletedScreenshot(result: ScreenshotSavedResult)
/**
@@ -75,17 +78,19 @@
private var result: ScreenshotSavedResult? = null
init {
- actionsCallback.providePreviewAction {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
- uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- actionExecutor.startSharedTransition(
- createEdit(result.uri, context),
- result.user,
- true
- )
+ actionsCallback.providePreviewAction(
+ PreviewAction(context.resources.getString(R.string.screenshot_edit_description)) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
+ uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
}
- }
+ )
actionsCallback.provideActionButton(
ActionButtonAppearance(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index d87d85b..59b47dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -16,16 +16,20 @@
package com.android.systemui.screenshot.appclips;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_TASK_ID;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.PERMISSION_SELF;
import android.app.Activity;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -43,6 +47,7 @@
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
+import android.widget.TextView;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
@@ -51,10 +56,13 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
+import java.util.Set;
+
import javax.inject.Inject;
/**
@@ -73,8 +81,6 @@
*
* <p>This {@link Activity} runs in its own separate process to isolate memory intensive image
* editing from SysUI process.
- *
- * TODO(b/267309532): Polish UI and animations.
*/
public class AppClipsActivity extends ComponentActivity {
@@ -94,6 +100,7 @@
private CropView mCropView;
private Button mSave;
private Button mCancel;
+ private TextView mBacklinksData;
private AppClipsViewModel mViewModel;
private ResultReceiver mResultReceiver;
@@ -153,11 +160,10 @@
mCancel = mLayout.findViewById(R.id.cancel);
mSave.setOnClickListener(this::onClick);
mCancel.setOnClickListener(this::onClick);
-
-
mCropView = mLayout.findViewById(R.id.crop_view);
-
+ mBacklinksData = mLayout.findViewById(R.id.backlinks_data);
mPreview = mLayout.findViewById(R.id.preview);
+
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateImageDimensions());
@@ -166,9 +172,19 @@
mViewModel.getScreenshot().observe(this, this::setScreenshot);
mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish);
+ mViewModel.getBacklinksLiveData().observe(this, this::setBacklinksData);
if (savedInstanceState == null) {
- mViewModel.performScreenshot();
+ int displayId = getDisplayId();
+ mViewModel.performScreenshot(displayId);
+
+ if (Flags.appClipsBacklinks()) {
+ int appClipsTaskId = getTaskId();
+ int callingPackageTaskId = intent.getIntExtra(EXTRA_CALLING_PACKAGE_TASK_ID,
+ INVALID_TASK_ID);
+ Set<Integer> taskIdsToIgnore = Set.of(appClipsTaskId, callingPackageTaskId);
+ mViewModel.triggerBacklinks(taskIdsToIgnore, displayId);
+ }
}
}
@@ -281,6 +297,15 @@
finish();
}
+ private void setBacklinksData(ClipData clipData) {
+ if (mBacklinksData.getVisibility() == View.GONE) {
+ mBacklinksData.setVisibility(View.VISIBLE);
+ }
+
+ mBacklinksData.setText(String.format(getString(R.string.backlinks_string),
+ clipData.getDescription().getLabel()));
+ }
+
private void setError(int errorCode) {
if (mResultReceiver == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index 7de22b1..aaa5dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.util.Log;
import androidx.annotation.Nullable;
@@ -27,19 +28,18 @@
import com.android.internal.infra.ServiceConnector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.settings.DisplayTracker;
import javax.inject.Inject;
/** An intermediary singleton object to help communicating with the cross process service. */
@SysUISingleton
class AppClipsCrossProcessHelper {
+ private static final String TAG = AppClipsCrossProcessHelper.class.getSimpleName();
private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
- private final DisplayTracker mDisplayTracker;
@Inject
- AppClipsCrossProcessHelper(@Application Context context, DisplayTracker displayTracker) {
+ AppClipsCrossProcessHelper(@Application Context context) {
// Start a service as main user so that even if the app clips activity is running as work
// profile user the service is able to use correct instance of Bubbles to grab a screenshot
// excluding the bubble layer.
@@ -48,7 +48,6 @@
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
| Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM,
IAppClipsScreenshotHelperService.Stub::asInterface);
- mDisplayTracker = displayTracker;
}
/**
@@ -58,15 +57,16 @@
* pass around but not a {@link Bitmap}.
*/
@Nullable
- Bitmap takeScreenshot() {
+ Bitmap takeScreenshot(int displayId) {
try {
AndroidFuture<ScreenshotHardwareBufferInternal> future =
mProxyConnector.postForResult(
- service ->
- // Take a screenshot of the default display of the user.
- service.takeScreenshot(mDisplayTracker.getDefaultDisplayId()));
+ service -> service.takeScreenshot(displayId));
return future.get().createBitmapThenCloseBuffer();
} catch (Exception e) {
+ Log.e(TAG,
+ String.format("Error while capturing a screenshot of displayId %d", displayId),
+ e);
return null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 48449b3..3c4469d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -85,6 +85,7 @@
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
+ static final String EXTRA_CALLING_PACKAGE_TASK_ID = TAG + "CALLING_PACKAGE_TASK_ID";
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private final NoteTaskController mNoteTaskController;
@@ -193,12 +194,14 @@
ComponentName componentName = ComponentName.unflattenFromString(
getString(R.string.config_screenshotAppClipsActivityComponent));
String callingPackageName = getCallingPackage();
+ int callingPackageTaskId = getTaskId();
Intent intent = new Intent()
.setComponent(componentName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
- .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
+ .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName)
+ .putExtra(EXTRA_CALLING_PACKAGE_TASK_ID, callingPackageTaskId);
try {
startActivity(intent);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 630d338..9bb7bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -16,9 +16,23 @@
package com.android.systemui.screenshot.appclips;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static com.google.common.util.concurrent.Futures.withTimeout;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.IActivityTaskManager;
+import android.app.WindowConfiguration;
+import android.app.assist.AssistContent;
+import android.content.ClipData;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
import android.graphics.RecordingCanvas;
@@ -26,10 +40,13 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@@ -37,22 +54,36 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
/** A {@link ViewModel} to help with the App Clips screenshot flow. */
final class AppClipsViewModel extends ViewModel {
+ private static final String TAG = AppClipsViewModel.class.getSimpleName();
+
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
+ private final IActivityTaskManager mAtmService;
+ private final AssistContentRequester mAssistContentRequester;
+ private final PackageManager mPackageManager;
+
@Main
private final Executor mMainExecutor;
@Background
@@ -61,24 +92,34 @@
private final MutableLiveData<Bitmap> mScreenshotLiveData;
private final MutableLiveData<Uri> mResultLiveData;
private final MutableLiveData<Integer> mErrorLiveData;
+ private final MutableLiveData<ClipData> mBacklinksLiveData;
- AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
- ImageExporter imageExporter, @Main Executor mainExecutor,
- @Background Executor bgExecutor) {
+ private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
+ ImageExporter imageExporter, IActivityTaskManager atmService,
+ AssistContentRequester assistContentRequester, PackageManager packageManager,
+ @Main Executor mainExecutor, @Background Executor bgExecutor) {
mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
mImageExporter = imageExporter;
+ mAtmService = atmService;
+ mAssistContentRequester = assistContentRequester;
+ mPackageManager = packageManager;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mScreenshotLiveData = new MutableLiveData<>();
mResultLiveData = new MutableLiveData<>();
mErrorLiveData = new MutableLiveData<>();
+ mBacklinksLiveData = new MutableLiveData<>();
}
- /** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
- void performScreenshot() {
+ /**
+ * Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link #getScreenshot()}.
+ *
+ * @param displayId id of the {@link Display} to capture screenshot.
+ */
+ void performScreenshot(int displayId) {
mBgExecutor.execute(() -> {
- Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
+ Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot(displayId);
mMainExecutor.execute(() -> {
if (screenshot == null) {
mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
@@ -89,6 +130,38 @@
});
}
+ /**
+ * Triggers the Backlinks flow which:
+ * <ul>
+ * <li>Evaluates the task to query.
+ * <li>Requests {@link AssistContent} from that task.
+ * <li>Transforms the {@link AssistContent} into {@link ClipData} for Backlinks.
+ * <li>The {@link ClipData} is reported to activity via {@link #getBacklinksLiveData()}.
+ * </ul>
+ *
+ * @param taskIdsToIgnore id of the tasks to ignore when querying for {@link AssistContent}
+ * @param displayId id of the display to query tasks for Backlinks data
+ */
+ void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) {
+ mBgExecutor.execute(() -> {
+ ListenableFuture<ClipData> backlinksData = getBacklinksData(taskIdsToIgnore, displayId);
+ Futures.addCallback(backlinksData, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable ClipData result) {
+ if (result != null) {
+ mBacklinksLiveData.setValue(result);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Error querying for Backlinks data", t);
+ }
+ }, mMainExecutor);
+
+ });
+ }
+
/** Returns a {@link LiveData} that holds the captured screenshot. */
LiveData<Bitmap> getScreenshot() {
return mScreenshotLiveData;
@@ -107,6 +180,11 @@
return mErrorLiveData;
}
+ /** Returns a {@link LiveData} that holds the Backlinks data in {@link ClipData}. */
+ LiveData<ClipData> getBacklinksLiveData() {
+ return mBacklinksLiveData;
+ }
+
/**
* Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
* {@link LiveData}.
@@ -148,21 +226,144 @@
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
+ private ListenableFuture<ClipData> getBacklinksData(Set<Integer> taskIdsToIgnore,
+ int displayId) {
+ return getAllRootTaskInfosOnDisplay(displayId)
+ .stream()
+ .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
+ .findFirst()
+ .map(this::getBacklinksDataForTaskId)
+ .orElse(Futures.immediateFuture(null));
+ }
+
+ private List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) {
+ try {
+ return mAtmService.getAllRootTaskInfosOnDisplay(displayId);
+ } catch (RemoteException e) {
+ Log.e(TAG, String.format("Error while querying for tasks on display %d", displayId), e);
+ return Collections.emptyList();
+ }
+ }
+
+ private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
+ // Only consider tasks that shouldn't be ignored, are visible, running, and have a launcher
+ // icon. Furthermore, types such as launcher/home/dock/assistant are ignored.
+ return !taskIdsToIgnore.contains(taskInfo.taskId)
+ && taskInfo.isVisible
+ && taskInfo.isRunning
+ && taskInfo.numActivities > 0
+ && taskInfo.topActivity != null
+ && taskInfo.topActivityInfo != null
+ && taskInfo.childTaskIds.length > 0
+ && taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ && canAppStartThroughLauncher(taskInfo.topActivity.getPackageName());
+ }
+
+ private boolean canAppStartThroughLauncher(String packageName) {
+ return getMainLauncherIntentForPackage(packageName).resolveActivity(mPackageManager)
+ != null;
+ }
+
+ private ListenableFuture<ClipData> getBacklinksDataForTaskId(RootTaskInfo taskInfo) {
+ SettableFuture<ClipData> backlinksData = SettableFuture.create();
+ int taskId = taskInfo.taskId;
+ mAssistContentRequester.requestAssistContent(taskId, assistContent ->
+ backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent)));
+ return withTimeout(backlinksData, 5L, TimeUnit.SECONDS, newSingleThreadScheduledExecutor());
+ }
+
+ /**
+ * A utility method to get {@link ClipData} to use for Backlinks functionality from
+ * {@link AssistContent} received from the app whose screenshot is taken.
+ *
+ * <p>There are multiple ways an app can provide deep-linkable data via {@link AssistContent}
+ * but Backlinks restricts to using only one way. The following is the ordered list based on
+ * preference:
+ * <ul>
+ * <li>{@link AssistContent#getWebUri()} is the most preferred way.
+ * <li>Second preference is given to {@link AssistContent#getIntent()} when the app provides
+ * the intent, see {@link AssistContent#isAppProvidedIntent()}.
+ * <li>The last preference is given to an {@link Intent} that is built using
+ * {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER}.
+ * </ul>
+ *
+ * @param taskInfo {@link RootTaskInfo} of the task which provided the {@link AssistContent}.
+ * @param content the {@link AssistContent} to map into Backlinks {@link ClipData}.
+ * @return {@link ClipData} that represents the Backlinks data.
+ */
+ private ClipData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
+ @Nullable AssistContent content) {
+ String appName = getAppNameOfTask(taskInfo);
+ String packageName = taskInfo.topActivity.getPackageName();
+ ClipData fallback = ClipData.newIntent(appName,
+ getMainLauncherIntentForPackage(packageName));
+ if (content == null) {
+ return fallback;
+ }
+
+ // First preference is given to app provided uri.
+ if (content.isAppProvidedWebUri()) {
+ Uri uri = content.getWebUri();
+ Intent backlinksIntent = new Intent(ACTION_VIEW).setData(uri);
+ if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ return ClipData.newRawUri(appName, uri);
+ }
+ }
+
+ // Second preference is given to app provided, hopefully deep-linking, intent.
+ if (content.isAppProvidedIntent()) {
+ Intent backlinksIntent = content.getIntent();
+ if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ return ClipData.newIntent(appName, backlinksIntent);
+ }
+ }
+
+ return fallback;
+ }
+
+ private boolean doesIntentResolveToSamePackage(Intent intentToResolve,
+ String requiredPackageName) {
+ ComponentName resolvedComponent = intentToResolve.resolveActivity(mPackageManager);
+ if (resolvedComponent == null) {
+ return false;
+ }
+
+ return resolvedComponent.getPackageName().equals(requiredPackageName);
+ }
+
+ private String getAppNameOfTask(RootTaskInfo taskInfo) {
+ return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
+ }
+
+ private Intent getMainLauncherIntentForPackage(String packageName) {
+ return new Intent(ACTION_MAIN)
+ .addCategory(CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+ }
+
/** Helper factory to help with injecting {@link AppClipsViewModel}. */
static final class Factory implements ViewModelProvider.Factory {
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
+ private final IActivityTaskManager mAtmService;
+ private final AssistContentRequester mAssistContentRequester;
+ private final PackageManager mPackageManager;
@Main
private final Executor mMainExecutor;
@Background
private final Executor mBgExecutor;
@Inject
- Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter,
- @Main Executor mainExecutor, @Background Executor bgExecutor) {
+ Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter,
+ IActivityTaskManager atmService, AssistContentRequester assistContentRequester,
+ PackageManager packageManager, @Main Executor mainExecutor,
+ @Background Executor bgExecutor) {
mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
mImageExporter = imageExporter;
+ mAtmService = atmService;
+ mAssistContentRequester = assistContentRequester;
+ mPackageManager = packageManager;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
}
@@ -176,7 +377,8 @@
//noinspection unchecked
return (T) new AppClipsViewModel(mAppClipsCrossProcessHelper, mImageExporter,
- mMainExecutor, mBgExecutor);
+ mAtmService, mAssistContentRequester, mPackageManager, mMainExecutor,
+ mBgExecutor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index 442b387..0fefa0b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -128,8 +128,9 @@
}
}
launch {
- viewModel.previewAction.collect { onClick ->
- previewView.setOnClickListener { onClick?.invoke() }
+ viewModel.previewAction.collect { action ->
+ previewView.setOnClickListener { action?.onClick?.invoke() }
+ previewView.contentDescription = action?.contentDescription
}
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index 3f99bc4..25420d4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -31,8 +31,8 @@
val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim
private val _badge = MutableStateFlow<Drawable?>(null)
val badge: StateFlow<Drawable?> = _badge
- private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
- val previewAction: StateFlow<(() -> Unit)?> = _previewAction
+ private val _previewAction = MutableStateFlow<PreviewAction?>(null)
+ val previewAction: StateFlow<PreviewAction?> = _previewAction
private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
val actions: StateFlow<List<ActionButtonViewModel>> = _actions
private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
@@ -57,8 +57,8 @@
_badge.value = badge
}
- fun setPreviewAction(onClick: () -> Unit) {
- _previewAction.value = onClick
+ fun setPreviewAction(previewAction: PreviewAction) {
+ _previewAction.value = previewAction
}
fun addAction(
@@ -149,6 +149,11 @@
}
}
+data class PreviewAction(
+ val contentDescription: CharSequence,
+ val onClick: () -> Unit,
+)
+
enum class AnimationState {
NOT_STARTED,
ENTRANCE_STARTED, // The first 200ms of the entrance animation
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 71fe371..1d43ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1044,6 +1044,10 @@
mView.setTranslationY(0f);
})
.start();
+ } else {
+ mView.postDelayed(() -> {
+ instantCollapse();
+ }, unlockAnimationStartDelay);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 95cabfb..1a7871a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -386,6 +386,11 @@
mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing());
}
+ @Nullable
+ public ViewGroup getIndicationArea() {
+ return mIndicationArea;
+ }
+
public void setIndicationArea(ViewGroup indicationArea) {
mIndicationArea = indicationArea;
mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text);
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 190a2cd..4ba673d 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
@@ -2011,6 +2011,21 @@
};
}
+ /**
+ * Retrieves an OnClickListener for the close button of a notification, which when invoked,
+ * dismisses the notificationc represented by the given ExpandableNotificationRow.
+ *
+ * @param row The ExpandableNotificationRow representing the notification to be dismissed.
+ * @return An OnClickListener instance that dismisses the notification(s) when invoked.
+ */
+ public View.OnClickListener getCloseButtonOnClickListener(ExpandableNotificationRow row) {
+ return v -> {
+ if (row != null) {
+ row.performDismiss(false);
+ }
+ };
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 9394249..f352123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -36,6 +36,7 @@
import com.android.app.animation.Interpolators;
import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.NotificationCloseButton;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.TransformableView;
@@ -60,6 +61,7 @@
= new PathInterpolator(0.4f, 0f, 0.7f, 1f);
protected final ViewTransformationHelper mTransformationHelper;
private CachingIconView mIcon;
+ private NotificationCloseButton mCloseButton;
private NotificationExpandButton mExpandButton;
private View mAltExpandTarget;
private View mIconContainer;
@@ -112,6 +114,7 @@
TRANSFORMING_VIEW_TITLE);
resolveHeaderViews();
addFeedbackOnClickListener(row);
+ addCloseButtonOnClickListener(row);
}
@Override
@@ -150,6 +153,7 @@
mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line);
mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
+ mCloseButton = mView.findViewById(com.android.internal.R.id.close_button);
}
private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
@@ -179,6 +183,13 @@
}
}
+ private void addCloseButtonOnClickListener(ExpandableNotificationRow row) {
+ View.OnClickListener listener = row.getCloseButtonOnClickListener(row);
+ if (mCloseButton != null && listener != null) {
+ mCloseButton.setOnClickListener(listener);
+ }
+ }
+
@Override
public void onContentUpdated(ExpandableNotificationRow row) {
super.onContentUpdated(row);
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 0e77ed4..38bcc0b 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
@@ -1181,9 +1181,11 @@
updateAlgorithmLayoutMinHeight();
updateOwnTranslationZ();
- // Give The Algorithm information regarding the QS height so it can layout notifications
- // properly. Needed for some devices that grows notifications down-to-top
- mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight());
+ if (!SceneContainerFlag.isEnabled()) {
+ // Give The Algorithm information regarding the QS height so it can layout notifications
+ // properly. Needed for some devices that grows notifications down-to-top
+ mStackScrollAlgorithm.updateQSFrameTop(mQsHeader == null ? 0 : mQsHeader.getHeight());
+ }
// Once the layout has finished, we don't need to animate any scrolling clampings anymore.
mAnimateStackYForContentHeightChange = false;
@@ -1811,6 +1813,7 @@
}
public void setQsHeader(ViewGroup qsHeader) {
+ SceneContainerFlag.assertInLegacyMode();
mQsHeader = qsHeader;
}
@@ -2662,6 +2665,7 @@
}
public void setMaxTopPadding(int maxTopPadding) {
+ SceneContainerFlag.assertInLegacyMode();
mMaxTopPadding = maxTopPadding;
}
@@ -2682,6 +2686,7 @@
}
public float getTopPaddingOverflow() {
+ SceneContainerFlag.assertInLegacyMode();
return mTopPaddingOverflow;
}
@@ -4641,6 +4646,7 @@
}
public boolean isEmptyShadeViewVisible() {
+ SceneContainerFlag.assertInLegacyMode();
return mEmptyShadeView.isVisible();
}
@@ -4919,6 +4925,7 @@
}
public void setQsFullScreen(boolean qsFullScreen) {
+ SceneContainerFlag.assertInLegacyMode();
if (FooterViewRefactor.isEnabled()) {
if (qsFullScreen == mQsFullScreen) {
return; // no change
@@ -5095,6 +5102,7 @@
}
public void setExpandingVelocity(float expandingVelocity) {
+ SceneContainerFlag.assertInLegacyMode();
mAmbientState.setExpandingVelocity(expandingVelocity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 7f1faee..cfcd6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -16,15 +16,23 @@
package com.android.systemui.volume.panel.component.spatial.domain.interactor
+import android.bluetooth.BluetoothProfile
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothProfile
+import com.android.settingslib.flags.Flags
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -33,6 +41,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/**
* Provides an ability to access and update spatial audio and head tracking state.
@@ -46,6 +55,8 @@
constructor(
audioOutputInteractor: AudioOutputInteractor,
private val spatializerInteractor: SpatializerInteractor,
+ private val audioRepository: AudioRepository,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
@VolumePanelScope private val coroutineScope: CoroutineScope,
) {
@@ -138,42 +149,85 @@
}
private suspend fun AudioOutputDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
- when (this) {
- is AudioOutputDevice.BuiltIn -> return builtinSpeaker
+ return when (this) {
+ is AudioOutputDevice.BuiltIn -> builtinSpeaker
is AudioOutputDevice.Bluetooth -> {
- return listOf(
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- cachedBluetoothDevice.address,
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- cachedBluetoothDevice.address,
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_BROADCAST,
- cachedBluetoothDevice.address,
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
- cachedBluetoothDevice.address,
- ),
- AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_HEARING_AID,
- cachedBluetoothDevice.address,
+ if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) {
+ getAudioDeviceAttributesByBluetoothProfile(cachedBluetoothDevice)
+ } else {
+ listOf(
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ cachedBluetoothDevice.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ cachedBluetoothDevice.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ cachedBluetoothDevice.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ cachedBluetoothDevice.address,
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ cachedBluetoothDevice.address,
+ )
)
- )
- .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+ .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+ }
}
- else -> return null
+ else -> null
}
}
+ private suspend fun getAudioDeviceAttributesByBluetoothProfile(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioDeviceAttributes? =
+ withContext(backgroundCoroutineContext) {
+ cachedBluetoothDevice.profiles
+ .firstOrNull {
+ it.profileId in audioProfiles && it.isEnabled(cachedBluetoothDevice.device)
+ }
+ ?.let { profile: LocalBluetoothProfile ->
+ when (profile.profileId) {
+ BluetoothProfile.A2DP -> {
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+ }
+ BluetoothProfile.LE_AUDIO -> {
+ when (
+ audioRepository.getBluetoothAudioDeviceCategory(
+ cachedBluetoothDevice.address
+ )
+ ) {
+ AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER ->
+ AudioDeviceInfo.TYPE_BLE_SPEAKER
+ else -> AudioDeviceInfo.TYPE_BLE_HEADSET
+ }
+ }
+ BluetoothProfile.HEARING_AID -> {
+ AudioDeviceInfo.TYPE_HEARING_AID
+ }
+ else -> null
+ }
+ }
+ ?.let {
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ it,
+ cachedBluetoothDevice.address,
+ )
+ }
+ }
+
private companion object {
val builtinSpeaker =
AudioDeviceAttributes(
@@ -181,5 +235,7 @@
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
""
)
+ val audioProfiles =
+ setOf(BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index f46b2f9..53b98d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -26,10 +26,14 @@
import android.hardware.input.InputManager;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.testing.TestableLooper;
import android.view.KeyEvent;
+import android.view.accessibility.Flags;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -44,6 +48,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -77,6 +82,9 @@
private SystemActions mSystemActions;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -131,4 +139,40 @@
verify(mTelecomManager).endCall();
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_GLOBAL_ACTION_MENU)
+ public void handleMenu_injectsKeyEvents() {
+ final List<KeyEvent> keyEvents = new ArrayList<>();
+ doAnswer(invocation -> {
+ keyEvents.add(new KeyEvent(invocation.getArgument(0)));
+ return null;
+ }).when(mInputManager).injectInputEvent(any(), anyInt());
+
+ mSystemActions.handleMenu();
+
+ assertThat(keyEvents.size()).isEqualTo(2);
+ assertThat(keyEvents.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
+ assertThat(keyEvents.get(0).getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(keyEvents.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MENU);
+ assertThat(keyEvents.get(1).getAction()).isEqualTo(KeyEvent.ACTION_UP);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_GLOBAL_ACTION_MEDIA_PLAY_PAUSE)
+ public void handleMediaPlayPause_injectsKeyEvents() {
+ final List<KeyEvent> keyEvents = new ArrayList<>();
+ doAnswer(invocation -> {
+ keyEvents.add(new KeyEvent(invocation.getArgument(0)));
+ return null;
+ }).when(mInputManager).injectInputEvent(any(), anyInt());
+
+ mSystemActions.handleMediaPlayPause();
+
+ assertThat(keyEvents.size()).isEqualTo(2);
+ assertThat(keyEvents.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ assertThat(keyEvents.get(0).getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+ assertThat(keyEvents.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+ assertThat(keyEvents.get(1).getAction()).isEqualTo(KeyEvent.ACTION_UP);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 38095c8..c30bedd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -258,8 +258,9 @@
setupEnabledAccessibilityServiceList();
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
assertThat(value).isEqualTo("");
}
@@ -274,8 +275,9 @@
ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
}
@@ -445,9 +447,11 @@
}
private void setupEnabledAccessibilityServiceList() {
- Settings.Secure.putString(mSpyContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+ TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString(),
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
+ );
final ResolveInfo resolveInfo = new ResolveInfo();
final ServiceInfo serviceInfo = new ServiceInfo();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt
new file mode 100644
index 0000000..c4eabd8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper
+import android.view.View
+import android.view.layoutInflater
+import android.view.mockedLayoutInflater
+import android.view.windowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.isNull
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AlternateBouncerViewBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mockedAltBouncerView =
+ spy(kosmos.layoutInflater.inflate(R.layout.alternate_bouncer, null, false))
+
+ @Before
+ fun setup() {
+ whenever(
+ kosmos.mockedLayoutInflater.inflate(
+ eq(R.layout.alternate_bouncer),
+ isNull(),
+ anyBoolean()
+ )
+ )
+ .thenReturn(mockedAltBouncerView)
+ kosmos.alternateBouncerViewBinder.start()
+ }
+
+ @Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ fun addViewToWindowManager() {
+ testScope.runTest {
+ kosmos.givenCanShowAlternateBouncer()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ verify(kosmos.windowManager).addView(any(), any())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ fun viewRemovedImmediatelyIfAlreadyAttachedToWindow() {
+ testScope.runTest {
+ kosmos.givenCanShowAlternateBouncer()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ verify(kosmos.windowManager).addView(any(), any())
+ whenever(mockedAltBouncerView.isAttachedToWindow).thenReturn(true)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ verify(kosmos.windowManager).removeView(any())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ fun viewNotRemovedUntilAttachedToWindow() {
+ testScope.runTest {
+ kosmos.givenCanShowAlternateBouncer()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.ALTERNATE_BOUNCER,
+ testScope,
+ )
+ verify(kosmos.windowManager).addView(any(), any())
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ verify(kosmos.windowManager, never()).removeView(any())
+ givenAltBouncerViewAttachedToWindow()
+ verify(kosmos.windowManager).removeView(any())
+ }
+ }
+
+ private fun givenAltBouncerViewAttachedToWindow() {
+ val attachStateChangeListenerCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(mockedAltBouncerView, atLeastOnce())
+ .addOnAttachStateChangeListener(attachStateChangeListenerCaptor.capture())
+ attachStateChangeListenerCaptor.allValues.onEach {
+ it.onViewAttachedToWindow(mockedAltBouncerView)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index 544350c..1d4b090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L,
- InstanceId.fakeInstanceId(-1), -1, false, null);
+ InstanceId.fakeInstanceId(-1), -1, false, null, -1, false);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 4da56b5..c974e0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -740,11 +740,8 @@
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
- val controlCommonModel =
- MediaCommonModel.MediaControl(
- MediaDataLoadingModel.Loaded(dataMain.instanceId),
- true
- )
+ val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
+ var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true)
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
repository.setOrderedMedia()
assertThat(currentMedia).containsExactly(controlCommonModel)
@@ -758,7 +755,15 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
+ val dataCurrentAndActive =
+ dataMain.copy(active = true, lastActive = clock.elapsedRealtime())
+ controlCommonModel =
+ controlCommonModel.copy(
+ mediaLoadingModel.copy(
+ receivedSmartspaceCardLatency = 100,
+ isSsReactivated = true
+ )
+ )
assertThat(currentMedia).containsExactly(controlCommonModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -800,11 +805,8 @@
val currentMedia by collectLastValue(repository.currentMedia)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
- val controlCommonModel =
- MediaCommonModel.MediaControl(
- MediaDataLoadingModel.Loaded(dataMain.instanceId),
- true
- )
+ val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
+ var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true)
val recsCommonModel =
MediaCommonModel.MediaRecommendations(
SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
@@ -824,7 +826,8 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
+ val dataCurrentAndActive =
+ dataMain.copy(active = true, lastActive = clock.elapsedRealtime())
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -849,6 +852,13 @@
)
.isTrue()
// Smartspace update should also be propagated but not prioritized.
+ controlCommonModel =
+ controlCommonModel.copy(
+ mediaLoadingModel.copy(
+ receivedSmartspaceCardLatency = 100,
+ isSsReactivated = true
+ )
+ )
assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel)
verify(listener)
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
@@ -909,7 +919,8 @@
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- val dataCurrentAndActive = dataCurrent.copy(active = true)
+ val dataCurrentAndActive =
+ dataMain.copy(active = true, lastActive = clock.elapsedRealtime())
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1063,11 +1074,8 @@
MediaCommonModel.MediaRecommendations(
SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
)
- val controlCommonModel =
- MediaCommonModel.MediaControl(
- MediaDataLoadingModel.Loaded(dataMain.instanceId),
- true
- )
+ val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
+ var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
@@ -1086,7 +1094,15 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
+ val dataCurrentAndActive =
+ dataMain.copy(active = true, lastActive = clock.elapsedRealtime())
+ controlCommonModel =
+ controlCommonModel.copy(
+ mediaLoadingModel.copy(
+ receivedSmartspaceCardLatency = 100,
+ isSsReactivated = true
+ )
+ )
verify(listener)
.onMediaDataLoaded(
eq(KEY),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 6f5c56e..148a2e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -24,6 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.ui.viewmodel.PreviewAction
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlin.test.Test
@@ -60,9 +61,9 @@
fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
actionsProvider = createActionsProvider()
- val previewActionCaptor = argumentCaptor<() -> Unit>()
+ val previewActionCaptor = argumentCaptor<PreviewAction>()
verify(actionsCallback).providePreviewAction(previewActionCaptor.capture())
- previewActionCaptor.firstValue.invoke()
+ previewActionCaptor.firstValue.onClick.invoke()
verifyNoMoreInteractions(actionExecutor)
}
@@ -102,14 +103,14 @@
fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
actionsProvider = createActionsProvider()
- val previewActionCaptor = argumentCaptor<() -> Unit>()
+ val previewActionCaptor = argumentCaptor<PreviewAction>()
verify(actionsCallback).providePreviewAction(previewActionCaptor.capture())
val actionButtonCaptor = argumentCaptor<() -> Unit>()
verify(actionsCallback, times(2))
.provideActionButton(any(), any(), actionButtonCaptor.capture())
actionButtonCaptor.firstValue.invoke()
- previewActionCaptor.firstValue.invoke()
+ previewActionCaptor.firstValue.onClick.invoke()
actionButtonCaptor.secondValue.invoke()
actionsProvider.setCompletedScreenshot(validResult)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
index 31560c6..29e7a8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.screenshot.ui.viewmodel.PreviewAction
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import java.util.UUID
import kotlin.test.Test
@@ -35,7 +36,7 @@
private val screenshotData = mock<ScreenshotData>()
private val actionExecutor = mock<ActionExecutor>()
private val viewModel = mock<ScreenshotViewModel>()
- private val onClick = mock<() -> Unit>()
+ private val previewAction = PreviewAction("description", onClick = {})
private lateinit var actionsController: ScreenshotActionsController
private lateinit var fakeActionsProvider1: FakeActionsProvider
@@ -43,6 +44,7 @@
private val actionsProviderFactory =
object : ScreenshotActionsProvider.Factory {
var isFirstCall = true
+
override fun create(
requestId: UUID,
request: ScreenshotData,
@@ -69,16 +71,16 @@
@Test
fun setPreview_onCurrentScreenshot_updatesViewModel() {
actionsController.setCurrentScreenshot(screenshotData)
- fakeActionsProvider1.callPreview(onClick)
+ fakeActionsProvider1.callPreview(previewAction)
- verify(viewModel).setPreviewAction(onClick)
+ verify(viewModel).setPreviewAction(previewAction)
}
@Test
fun setPreview_onNonCurrentScreenshot_doesNotUpdateViewModel() {
actionsController.setCurrentScreenshot(screenshotData)
actionsController.setCurrentScreenshot(screenshotData)
- fakeActionsProvider1.callPreview(onClick)
+ fakeActionsProvider1.callPreview(previewAction)
verify(viewModel, never()).setPreviewAction(any())
}
@@ -87,8 +89,8 @@
private val actionsCallback: ScreenshotActionsController.ActionsCallback
) : ScreenshotActionsProvider {
- fun callPreview(onClick: () -> Unit) {
- actionsCallback.providePreviewAction(onClick)
+ fun callPreview(previewAction: PreviewAction) {
+ actionsCallback.providePreviewAction(previewAction)
}
override fun onScrollChipReady(onClick: Runnable) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 68a6893..2981590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.appclips;
import static android.app.Activity.RESULT_OK;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
@@ -25,30 +26,45 @@
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.IActivityTaskManager;
+import android.app.assist.AssistContent;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.view.Display;
+import android.view.View;
import android.widget.ImageView;
+import android.widget.TextView;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.intercepting.SingleActivityFactory;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
+import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
import com.android.systemui.settings.UserTracker;
@@ -60,9 +76,11 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@@ -75,14 +93,27 @@
private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
private static final String TEST_URI_STRING = "www.test-uri.com";
private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
- private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> {};
+ private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> {
+ };
private static final String TEST_CALLING_PACKAGE = "test-calling-package";
+ private static final int BACKLINKS_TASK_ID = 42;
+ private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app";
+ private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
+ private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
+ createTaskInfoForBacklinksTask();
+
+ private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK =
+ createAssistContentForBacklinksTask();
@Mock
private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@Mock
private ImageExporter mImageExporter;
@Mock
+ private IActivityTaskManager mAtmService;
+ @Mock
+ private AssistContentRequester mAssistContentRequester;
+ @Mock
private PackageManager mPackageManager;
@Mock
private UserTracker mUserTracker;
@@ -98,9 +129,10 @@
protected AppClipsActivityTestable create(Intent unUsed) {
return new AppClipsActivityTestable(
new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper,
- mImageExporter, getContext().getMainExecutor(),
- directExecutor()), mPackageManager, mUserTracker,
- mUiEventLogger);
+ mImageExporter, mAtmService, mAssistContentRequester,
+ mPackageManager, getContext().getMainExecutor(),
+ directExecutor()),
+ mPackageManager, mUserTracker, mUiEventLogger);
}
};
@@ -118,7 +150,7 @@
when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
any(ApplicationInfoFlags.class), eq(TEST_USER_ID))).thenReturn(applicationInfo);
- when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(TEST_BITMAP);
+ when(mAppClipsCrossProcessHelper.takeScreenshot(anyInt())).thenReturn(TEST_BITMAP);
ImageExporter.Result result = new ImageExporter.Result();
result.uri = TEST_URI;
when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class),
@@ -132,10 +164,13 @@
}
@Test
+ @DisableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
public void appClipsLaunched_screenshotDisplayed() {
launchActivity();
assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
+ assertThat(mActivity.findViewById(R.id.backlinks_data).getVisibility())
+ .isEqualTo(View.GONE);
}
@Test
@@ -176,6 +211,32 @@
verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_CANCELLED, TEST_UID, TEST_CALLING_PACKAGE);
}
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_displayed() throws RemoteException {
+ // Set up mocking to verify backlinks view is displayed on screen.
+ ArgumentCaptor<Integer> displayIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ when(mAtmService.getAllRootTaskInfosOnDisplay(displayIdCaptor.capture()))
+ .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS));
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(ASSIST_CONTENT_FOR_BACKLINKS_TASK);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ when(mPackageManager
+ .resolveActivity(any(Intent.class), anyInt()))
+ .thenReturn(createBacklinksTaskResolveInfo());
+
+ launchActivity();
+ waitForIdleSync();
+
+ assertThat(displayIdCaptor.getValue()).isEqualTo(mActivity.getDisplayId());
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(backlinksData.getText().toString()).isEqualTo(
+ mActivity.getString(R.string.backlinks_string, BACKLINKS_TASK_APP_NAME));
+ }
+
private void launchActivity() {
launchActivity(createResultReceiver(FAKE_CONSUMER));
}
@@ -203,7 +264,7 @@
testReceiver.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- testReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
+ testReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
parcel.recycle();
return testReceiver;
}
@@ -212,6 +273,37 @@
mContext.getMainExecutor().execute(runnable);
}
+ private static ResolveInfo createBacklinksTaskResolveInfo() {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ activityInfo.name = BACKLINKS_TASK_APP_NAME;
+ activityInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME;
+ activityInfo.applicationInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME;
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = activityInfo;
+ return resolveInfo;
+ }
+
+ private static RootTaskInfo createTaskInfoForBacklinksTask() {
+ RootTaskInfo taskInfo = new RootTaskInfo();
+ taskInfo.taskId = BACKLINKS_TASK_ID;
+ taskInfo.isVisible = true;
+ taskInfo.isRunning = true;
+ taskInfo.numActivities = 1;
+ taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
+ taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
+ taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
+ taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ return taskInfo;
+ }
+
+ private static AssistContent createAssistContentForBacklinksTask() {
+ AssistContent content = new AssistContent();
+ content.setWebUri(Uri.parse("https://developers.android.com"));
+ return content;
+ }
+
public static class AppClipsActivityTestable extends AppClipsActivity {
public AppClipsActivityTestable(AppClipsViewModel.Factory viewModelFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index ad0797c..dcb75d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -16,28 +16,51 @@
package com.android.systemui.screenshot.appclips;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
+import static android.content.ClipDescription.MIMETYPE_TEXT_URILIST;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.IActivityTaskManager;
+import android.app.assist.AssistContent;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.net.Uri;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
-import android.view.Display;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
import com.google.common.util.concurrent.Futures;
@@ -45,9 +68,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -60,27 +87,44 @@
private static final Rect FAKE_RECT = new Rect();
private static final Uri FAKE_URI = Uri.parse("www.test-uri.com");
private static final UserHandle USER_HANDLE = Process.myUserHandle();
+ private static final int BACKLINKS_TASK_ID = 42;
+ private static final String BACKLINKS_TASK_APP_NAME = "Ultimate question app";
+ private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
+ private static final AssistContent EMPTY_ASSIST_CONTENT = new AssistContent();
@Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@Mock private ImageExporter mImageExporter;
+ @Mock private IActivityTaskManager mAtmService;
+ @Mock private AssistContentRequester mAssistContentRequester;
+ @Mock
+ private PackageManager mPackageManager;
+ private ArgumentCaptor<Intent> mPackageManagerIntentCaptor;
private AppClipsViewModel mViewModel;
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mPackageManagerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ // Set up mocking for backlinks.
+ when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ .thenReturn(List.of(createTaskInfoForBacklinksTask()));
+ when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt()))
+ .thenReturn(createBacklinksTaskResolveInfo());
mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
+ mAtmService, mAssistContentRequester, mPackageManager,
getContext().getMainExecutor(), directExecutor()).create(AppClipsViewModel.class);
}
@Test
public void performScreenshot_fails_shouldUpdateErrorWithFailed() {
- when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(null);
+ when(mAppClipsCrossProcessHelper.takeScreenshot(anyInt())).thenReturn(null);
- mViewModel.performScreenshot();
+ mViewModel.performScreenshot(DEFAULT_DISPLAY);
waitForIdleSync();
- verify(mAppClipsCrossProcessHelper).takeScreenshot();
+ verify(mAppClipsCrossProcessHelper).takeScreenshot(DEFAULT_DISPLAY);
assertThat(mViewModel.getErrorLiveData().getValue())
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
assertThat(mViewModel.getResultLiveData().getValue()).isNull();
@@ -88,12 +132,12 @@
@Test
public void performScreenshot_succeeds_shouldUpdateScreenshotWithBitmap() {
- when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(FAKE_BITMAP);
+ when(mAppClipsCrossProcessHelper.takeScreenshot(DEFAULT_DISPLAY)).thenReturn(FAKE_BITMAP);
- mViewModel.performScreenshot();
+ mViewModel.performScreenshot(DEFAULT_DISPLAY);
waitForIdleSync();
- verify(mAppClipsCrossProcessHelper).takeScreenshot();
+ verify(mAppClipsCrossProcessHelper).takeScreenshot(DEFAULT_DISPLAY);
assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
assertThat(mViewModel.getScreenshot().getValue()).isEqualTo(FAKE_BITMAP);
}
@@ -101,7 +145,7 @@
@Test
public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() {
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE),
- eq(Display.DEFAULT_DISPLAY))).thenReturn(
+ eq(DEFAULT_DISPLAY))).thenReturn(
Futures.immediateFailedFuture(new ExecutionException(new Throwable())));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
@@ -115,7 +159,7 @@
@Test
public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() {
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE),
- eq(Display.DEFAULT_DISPLAY))).thenReturn(
+ eq(DEFAULT_DISPLAY))).thenReturn(
Futures.immediateFuture(new ImageExporter.Result()));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
@@ -131,7 +175,7 @@
ImageExporter.Result result = new ImageExporter.Result();
result.uri = FAKE_URI;
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), eq(USER_HANDLE),
- eq(Display.DEFAULT_DISPLAY))).thenReturn(Futures.immediateFuture(result));
+ eq(DEFAULT_DISPLAY))).thenReturn(Futures.immediateFuture(result));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
waitForIdleSync();
@@ -139,4 +183,198 @@
assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
assertThat(mViewModel.getResultLiveData().getValue()).isEqualTo(FAKE_URI);
}
+
+ @Test
+ public void triggerBacklinks_shouldUpdateBacklinks_withUri() {
+ Uri expectedUri = Uri.parse("https://developers.android.com");
+ AssistContent contentWithUri = new AssistContent();
+ contentWithUri.setWebUri(expectedUri);
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(contentWithUri);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
+ assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
+ assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
+
+ ClipData result = mViewModel.getBacklinksLiveData().getValue();
+ ClipDescription resultDescription = result.getDescription();
+ assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST);
+ assertThat(result.getItemCount()).isEqualTo(1);
+ assertThat(result.getItemAt(0).getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ public void triggerBacklinks_withNonResolvableUri_usesMainLauncherIntent() {
+ Uri expectedUri = Uri.parse("https://developers.android.com");
+ AssistContent contentWithUri = new AssistContent();
+ contentWithUri.setWebUri(expectedUri);
+ resetPackageManagerMockingForUsingFallbackBacklinks();
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(contentWithUri);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ verifyMainLauncherBacklinksIntent();
+ }
+
+ @Test
+ public void triggerBacklinks_shouldUpdateBacklinks_withAppProvidedIntent() {
+ Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
+ AssistContent contentWithAppProvidedIntent = new AssistContent();
+ contentWithAppProvidedIntent.setIntent(expectedIntent);
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(contentWithAppProvidedIntent);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
+ assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
+
+ ClipData result = mViewModel.getBacklinksLiveData().getValue();
+ ClipDescription resultDescription = result.getDescription();
+ assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT);
+ assertThat(result.getItemCount()).isEqualTo(1);
+ assertThat(result.getItemAt(0).getIntent()).isEqualTo(expectedIntent);
+ }
+
+ @Test
+ public void triggerBacklinks_withNonResolvableAppProvidedIntent_usesMainLauncherIntent() {
+ Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
+ AssistContent contentWithAppProvidedIntent = new AssistContent();
+ contentWithAppProvidedIntent.setIntent(expectedIntent);
+ resetPackageManagerMockingForUsingFallbackBacklinks();
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(contentWithAppProvidedIntent);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ verifyMainLauncherBacklinksIntent();
+ }
+
+ @Test
+ public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() {
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
+ assertThat(queriedIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
+ assertThat(queriedIntent.getAction()).isEqualTo(ACTION_MAIN);
+ assertThat(queriedIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+
+ verifyMainLauncherBacklinksIntent();
+ }
+
+ @Test
+ public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() {
+ reset(mPackageManager);
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
+ }
+
+ @Test
+ public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable()
+ throws RemoteException {
+ reset(mAtmService);
+ RootTaskInfo taskInfo = createTaskInfoForBacklinksTask();
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
+ when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ .thenReturn(List.of(taskInfo));
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
+ }
+
+ @Test
+ public void triggerBacklinks_taskIdsToIgnoreConsidered_noBacklinkAvailable() {
+ mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
+ }
+
+ private void resetPackageManagerMockingForUsingFallbackBacklinks() {
+ reset(mPackageManager);
+ when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
+ // First the logic queries whether a package has a launcher activity, this should
+ // resolve otherwise the logic filters out the task.
+ .thenReturn(createBacklinksTaskResolveInfo())
+ // Then logic queries with the backlinks intent, this should not resolve for the
+ // logic to use the fallback intent.
+ .thenReturn(null);
+ }
+
+ private void verifyMainLauncherBacklinksIntent() {
+ ClipData result = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(result.getItemCount()).isEqualTo(1);
+
+ ClipDescription resultDescription = result.getDescription();
+ assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT);
+
+ Intent actualBacklinksIntent = result.getItemAt(0).getIntent();
+ assertThat(actualBacklinksIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
+ assertThat(actualBacklinksIntent.getAction()).isEqualTo(ACTION_MAIN);
+ assertThat(actualBacklinksIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
+ }
+
+ private static ResolveInfo createBacklinksTaskResolveInfo() {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = new ApplicationInfo();
+ activityInfo.name = BACKLINKS_TASK_APP_NAME;
+ activityInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME;
+ activityInfo.applicationInfo.packageName = BACKLINKS_TASK_PACKAGE_NAME;
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = activityInfo;
+ return resolveInfo;
+ }
+
+ private static RootTaskInfo createTaskInfoForBacklinksTask() {
+ RootTaskInfo taskInfo = new RootTaskInfo();
+ taskInfo.taskId = BACKLINKS_TASK_ID;
+ taskInfo.isVisible = true;
+ taskInfo.isRunning = true;
+ taskInfo.numActivities = 1;
+ taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
+ taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
+ taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
+ taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ return taskInfo;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 7304bd6..0766afc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -793,6 +793,73 @@
}
@Test
+ public void isExpanded_onKeyguard_allowOnKeyguardExpanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(true);
+ row.setUserExpanded(true);
+
+ // THEN
+ assertThat(row.isExpanded(/*allowOnKeyguard =*/ true)).isTrue();
+ }
+ @Test
+ public void isExpanded_onKeyguard_notAllowOnKeyguardNotExpanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(true);
+ row.setUserExpanded(true);
+
+ // THEN
+ assertThat(row.isExpanded(/*allowOnKeyguard =*/ false)).isFalse();
+ }
+
+ @Test
+ public void isExpanded_systemExpanded_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void isExpanded_systemChildExpanded_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemChildExpanded(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void isExpanded_userExpanded_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(true);
+ row.setUserExpanded(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void isExpanded_userExpandedFalse_notExpanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(true);
+ row.setUserExpanded(false);
+
+ // THEN
+ assertThat(row.isExpanded()).isFalse();
+ }
+
+ @Test
public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
throws Exception {
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
index 21dea6b..2ee289b 100644
--- a/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/LayoutInflaterKosmos.kt
@@ -18,6 +18,8 @@
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
var Kosmos.layoutInflater: LayoutInflater by
Kosmos.Fixture { LayoutInflater.from(applicationContext) }
+var Kosmos.mockedLayoutInflater: LayoutInflater by Kosmos.Fixture { mock<LayoutInflater>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index f75cdd4..9236bd2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -28,6 +29,7 @@
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.systemClock
val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by
@@ -47,3 +49,24 @@
sceneInteractor = { sceneInteractor },
)
}
+
+fun Kosmos.givenCanShowAlternateBouncer() {
+ this.givenAlternateBouncerSupported()
+ this.keyguardBouncerRepository.setPrimaryShow(false)
+ this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ this.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ whenever(this.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+ whenever(this.keyguardStateController.isUnlocked).thenReturn(false)
+}
+
+fun Kosmos.givenAlternateBouncerSupported() {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ this.fingerprintPropertyRepository.supportsUdfps()
+ } else {
+ this.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true)
+ }
+}
+
+fun Kosmos.givenCannotShowAlternateBouncer() {
+ this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
index d3ed58b..1da1fb2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -17,16 +17,28 @@
package com.android.systemui.communal.data.repository
+import android.content.pm.UserInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
/** Fake implementation of [CommunalPrefsRepository] */
class FakeCommunalPrefsRepository : CommunalPrefsRepository {
- private val _isCtaDismissed = MutableStateFlow(false)
- override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow()
+ private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
+ private val _isDisclaimerDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
- override suspend fun setCtaDismissedForCurrentUser() {
- _isCtaDismissed.value = true
+ override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
+ _isCtaDismissed.map { it.contains(user) }
+
+ override fun isDisclaimerDismissed(user: UserInfo): Flow<Boolean> =
+ _isDisclaimerDismissed.map { it.contains(user) }
+
+ override suspend fun setCtaDismissed(user: UserInfo) {
+ _isCtaDismissed.value = _isCtaDismissed.value.toMutableSet().apply { add(user) }
+ }
+
+ override suspend fun setDisclaimerDismissed(user: UserInfo) {
+ _isDisclaimerDismissed.value =
+ _isDisclaimerDismissed.value.toMutableSet().apply { add(user) }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 1583d1c..b58861b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -19,7 +19,6 @@
import android.os.userManager
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
-import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.flags.Flags
@@ -46,7 +45,7 @@
broadcastDispatcher = broadcastDispatcher,
communalSceneInteractor = communalSceneInteractor,
widgetRepository = communalWidgetRepository,
- communalPrefsRepository = communalPrefsRepository,
+ communalPrefsInteractor = communalPrefsInteractor,
mediaRepository = communalMediaRepository,
smartspaceRepository = smartspaceRepository,
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt
new file mode 100644
index 0000000..37563c4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalPrefsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.settings.userTracker
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.communalPrefsInteractor: CommunalPrefsInteractor by
+ Kosmos.Fixture {
+ CommunalPrefsInteractor(
+ applicationCoroutineScope,
+ communalPrefsRepository,
+ selectedUserInteractor,
+ userTracker,
+ tableLogBuffer = mock()
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
new file mode 100644
index 0000000..6eb8a49
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.keyguard.ui.binder
+
+import android.content.applicationContext
+import android.view.layoutInflater
+import android.view.mockedLayoutInflater
+import android.view.windowManager
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerWindowViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.alternateBouncerViewBinder by
+ Kosmos.Fixture {
+ AlternateBouncerViewBinder(
+ applicationScope = applicationCoroutineScope,
+ alternateBouncerWindowViewModel = { alternateBouncerWindowViewModel },
+ alternateBouncerDependencies = { alternateBouncerDependencies },
+ windowManager = { windowManager },
+ layoutInflater = { mockedLayoutInflater },
+ )
+ }
+
+private val Kosmos.alternateBouncerDependencies by
+ Kosmos.Fixture {
+ AlternateBouncerDependencies(
+ viewModel = mock<AlternateBouncerViewModel>(),
+ swipeUpAnywhereGestureHandler = mock<SwipeUpAnywhereGestureHandler>(),
+ tapGestureDetector = mock<TapGestureDetector>(),
+ udfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
+ udfpsAccessibilityOverlayViewModel = {
+ mock<AlternateBouncerUdfpsAccessibilityOverlayViewModel>()
+ },
+ messageAreaViewModel = mock<AlternateBouncerMessageAreaViewModel>(),
+ powerInteractor = powerInteractor,
+ )
+ }
+
+private val Kosmos.alternateBouncerUdfpsIconViewModel by
+ Kosmos.Fixture {
+ AlternateBouncerUdfpsIconViewModel(
+ context = applicationContext,
+ configurationInteractor = configurationInteractor,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ deviceEntryBackgroundViewModel = mock<DeviceEntryBackgroundViewModel>(),
+ fingerprintPropertyInteractor = fingerprintPropertyInteractor,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ alternateBouncerViewModel = alternateBouncerViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 690bde7..7a04aa2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -18,6 +18,7 @@
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.mediaSmartspaceLogger
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.util.time.systemClock
@@ -26,6 +27,7 @@
MediaFilterRepository(
applicationContext = applicationContext,
systemClock = systemClock,
- configurationController = configurationController
+ configurationController = configurationController,
+ smartspaceLogger = mediaSmartspaceLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt
new file mode 100644
index 0000000..c63dec5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaSmartspaceLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.media.controls.util
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+var Kosmos.mediaSmartspaceLogger by Kosmos.Fixture { MediaSmartspaceLogger() }
+val Kosmos.mockMediaSmartspaceLogger by Kosmos.Fixture { mock(MediaSmartspaceLogger::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 21d59f0..fcea9e7b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -42,6 +42,7 @@
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
+ private val deviceCategories: MutableMap<String, Int> = mutableMapOf()
private fun getAudioStreamModelState(
audioStream: AudioStream
@@ -103,4 +104,12 @@
override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
mutableRingerMode.value = mode
}
+
+ fun setBluetoothAudioDeviceCategory(bluetoothAddress: String, category: Int) {
+ deviceCategories[bluetoothAddress] = category
+ }
+
+ override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
+ return deviceCategories[bluetoothAddress] ?: AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt
index 95a7b9b..6a46d56 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt
@@ -17,8 +17,10 @@
package com.android.systemui.volume.panel.component.spatial.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.volume.data.repository.audioRepository
import com.android.systemui.volume.domain.interactor.audioOutputInteractor
val Kosmos.spatialAudioComponentInteractor by
@@ -26,6 +28,8 @@
SpatialAudioComponentInteractor(
audioOutputInteractor,
spatializerInteractor,
+ audioRepository,
+ backgroundCoroutineContext,
testScope.backgroundScope,
)
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 7f542d1..e8db80a 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -310,6 +310,10 @@
// Package: android
NOTE_USB_UVC = 75;
+ // Inform the user about adaptive notifications
+ // Package: com.android.systemui
+ NOTE_ADAPTIVE_NOTIFICATIONS = 76;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 9747579..2945af5 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -34,6 +34,7 @@
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.Flags;
import com.android.internal.R;
import com.android.internal.accessibility.util.AccessibilityUtils;
@@ -328,6 +329,18 @@
sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER,
InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD);
return true;
+ case AccessibilityService.GLOBAL_ACTION_MENU:
+ if (Flags.globalActionMenu()) {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MENU,
+ InputDevice.SOURCE_KEYBOARD);
+ }
+ return true;
+ case AccessibilityService.GLOBAL_ACTION_MEDIA_PLAY_PAUSE:
+ if (Flags.globalActionMediaPlayPause()) {
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
+ InputDevice.SOURCE_KEYBOARD);
+ }
+ return true;
default:
Slog.e(TAG, "Invalid action id: " + actionId);
return false;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 3fbd856..b3a2da4 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -19,7 +19,6 @@
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
-import static android.companion.CompanionDeviceManager.REASON_INTERNAL_ERROR;
import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -183,7 +182,7 @@
String errorMessage = "3p apps are not allowed to create associations on watch.";
Slog.e(TAG, errorMessage);
try {
- callback.onFailure(errorMessage);
+ callback.onFailure(RESULT_INTERNAL_ERROR);
} catch (RemoteException e) {
// ignored
}
@@ -252,8 +251,9 @@
} catch (SecurityException e) {
// Since, at this point the caller is our own UI, we need to catch the exception on
// forward it back to the application via the callback.
+ Slog.e(TAG, e.getMessage());
try {
- callback.onFailure(e.getMessage());
+ callback.onFailure(RESULT_INTERNAL_ERROR);
} catch (RemoteException ignore) {
}
return;
@@ -378,7 +378,7 @@
// Send the association back via the app's callback
if (callback != null) {
try {
- callback.onFailure(REASON_INTERNAL_ERROR);
+ callback.onFailure(RESULT_INTERNAL_ERROR);
} catch (RemoteException ignore) {
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ce4067c..ef39846 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -192,7 +192,8 @@
for (UserInfo user : aliveUsers) {
int userId = user.getUserHandle().getIdentifier();
int appUid = queryUidFromPackageName(userId, packageName);
- if (mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) {
+ if (mVirtualDeviceManagerInternal != null
+ && mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) {
if (data == null) {
data = new InjectionSessionData();
data.appUid = appUid;
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index cf48180..0655685 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -229,6 +229,10 @@
Slog.e(TAG, "No sensor callback configured for sensor handle " + handle);
return BAD_VALUE;
}
+ if (mVdmInternal == null) {
+ Slog.e(TAG, "Virtual Device Manager is not enabled.");
+ return BAD_VALUE;
+ }
VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, handle);
if (sensor == null) {
Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId
@@ -285,6 +289,10 @@
Slog.e(TAG, "No runtime sensor callback configured.");
return BAD_VALUE;
}
+ if (mVdmInternal == null) {
+ Slog.e(TAG, "Virtual Device Manager is not enabled.");
+ return BAD_VALUE;
+ }
VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, sensorHandle);
if (sensor == null) {
Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 022df9a..195e94b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10235,19 +10235,6 @@
addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
}
- @Override
- public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs,
- long framePresentedTimeNs) {
- int callingUid = Binder.getCallingUid();
- int userId = UserHandle.getUserId(callingUid);
- addStartInfoTimestampInternal(
- ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME,
- renderThreadDrawStartTimeNs, userId, callingUid);
- addStartInfoTimestampInternal(
- ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE,
- framePresentedTimeNs, userId, callingUid);
- }
-
private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
mProcessList.getAppStartInfoTracker().addTimestampToStart(
Settings.getPackageNameForUid(mContext, uid),
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 4a7ad31..3042b2a 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -1195,8 +1195,21 @@
// Records are sorted newest to oldest, grab record at index 0.
ApplicationStartInfo startInfo = mInfos.get(0);
+ int startupState = startInfo.getStartupState();
- if (!isAddTimestampAllowed(startInfo, key, timestampNs)) {
+ // If startup state is error then don't accept any further timestamps.
+ if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
+ if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
+ return;
+ }
+
+ // If startup state is first frame drawn then only accept fully drawn timestamp.
+ if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN
+ && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) {
+ if (DEBUG) {
+ Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully "
+ + "drawn, not accepting new timestamps.");
+ }
return;
}
@@ -1209,55 +1222,6 @@
}
}
- private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key,
- long timestampNs) {
- int startupState = startInfo.getStartupState();
-
- // If startup state is error then don't accept any further timestamps.
- if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
- if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
- return false;
- }
-
- Map<Integer, Long> timestamps = startInfo.getStartupTimestamps();
-
- if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
- switch (key) {
- case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN:
- // Allowed, continue to confirm it's not already added.
- break;
- case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME:
- Long firstFrameTimeNs = timestamps
- .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
- if (firstFrameTimeNs == null) {
- // This should never happen. State can't be first frame drawn if first
- // frame timestamp was not provided.
- return false;
- }
-
- if (timestampNs > firstFrameTimeNs) {
- // Initial renderthread frame has to occur before first frame.
- return false;
- }
-
- // Allowed, continue to confirm it's not already added.
- break;
- case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE:
- // Allowed, continue to confirm it's not already added.
- break;
- default:
- return false;
- }
- }
-
- if (timestamps.get(key) != null) {
- // Timestamp should not occur more than once for a given start.
- return false;
- }
-
- return true;
- }
-
@GuardedBy("mLock")
void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
if (mMonitoringModeEnabled) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 178171d..aeebae4 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -2482,7 +2482,8 @@
ipw.println();
if (dumpConstants) {
- mConstants.dump(ipw);
+ mFgConstants.dump(ipw);
+ mBgConstants.dump(ipw);
}
if (dumpHistory) {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a8b9e43..4ff1367 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -674,7 +674,9 @@
if (conn != null) {
conn.waiting = true;
}
- cpr.wait(wait);
+ if (wait > 0) {
+ cpr.wait(wait);
+ }
if (cpr.provider == null) {
timedOut = true;
break;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index fa0e2ca..30efa3e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2335,6 +2335,14 @@
// Never stop system user
return;
}
+ synchronized(mLock) {
+ final UserState uss = mStartedUsers.get(oldUserId);
+ if (uss == null || uss.state == UserState.STATE_STOPPING
+ || uss.state == UserState.STATE_SHUTDOWN) {
+ // We've stopped (or are stopping) the user anyway, so don't bother scheduling.
+ return;
+ }
+ }
if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) {
// MainUser is currently special for things like Docking, so we'll exempt it for now.
Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue "
@@ -2371,6 +2379,12 @@
// We'll soon want to switch to this user, so don't kill it now.
return;
}
+ final UserInfo currentOrTargetUser = getCurrentUserLU();
+ if (currentOrTargetUser != null && currentOrTargetUser.isGuest()) {
+ // Don't kill any background users for the sake of a Guest. Just reschedule instead.
+ scheduleStopOfBackgroundUser(userId);
+ return;
+ }
Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1bb7922..f61bd60 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2790,8 +2790,9 @@
* have information on them.
*/
private static boolean isOpAllowedForUid(int uid) {
+ int appId = UserHandle.getAppId(uid);
return Flags.runtimePermissionAppopsMappingEnabled()
- && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
+ && (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID);
}
@Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 0afca92..73aa14b 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -251,7 +251,7 @@
}
private void registerVirtualDeviceListener() {
- if (mVirtualDeviceListener != null) {
+ if (mVdm == null || mVirtualDeviceListener != null) {
return;
}
mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {
@@ -891,7 +891,8 @@
Slog.e(TAG, "RemoteException calling UserManager: " + e);
return null;
}
- if (deviceId != DEVICE_ID_DEFAULT && !mVdm.isValidVirtualDeviceId(deviceId)) {
+ if (deviceId != DEVICE_ID_DEFAULT
+ && mVdm != null && !mVdm.isValidVirtualDeviceId(deviceId)) {
Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId "
+ deviceId);
return null;
@@ -1467,8 +1468,8 @@
return;
}
// Don't notify if this access is coming from the privileged app which owns the device.
- if (clipboard.deviceId != DEVICE_ID_DEFAULT && mVdmInternal.getDeviceOwnerUid(
- clipboard.deviceId) == uid) {
+ if (clipboard.deviceId != DEVICE_ID_DEFAULT && mVdmInternal != null
+ && mVdmInternal.getDeviceOwnerUid(clipboard.deviceId) == uid) {
return;
}
// Don't notify if already notified for this uid and clip.
@@ -1519,7 +1520,7 @@
private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException {
ArraySet<Context> contexts = new ArraySet<>();
- if (clipboard.deviceId != DEVICE_ID_DEFAULT) {
+ if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) {
DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2d5f38e..e686779 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1548,16 +1548,20 @@
int flags = virtualDisplayConfig.getFlags();
if (virtualDevice != null) {
final VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
- try {
- if (!vdm.isValidVirtualDeviceId(virtualDevice.getDeviceId())) {
- throw new SecurityException("Invalid virtual device");
+ if (vdm != null) {
+ try {
+ if (!vdm.isValidVirtualDeviceId(virtualDevice.getDeviceId())) {
+ throw new SecurityException("Invalid virtual device");
+ }
+ } catch (RemoteException ex) {
+ throw new SecurityException("Unable to validate virtual device");
}
- } catch (RemoteException ex) {
- throw new SecurityException("Unable to validate virtual device");
+ final VirtualDeviceManagerInternal localVdm =
+ getLocalService(VirtualDeviceManagerInternal.class);
+ if (localVdm != null) {
+ flags |= localVdm.getBaseVirtualDisplayFlags(virtualDevice);
+ }
}
- final VirtualDeviceManagerInternal localVdm =
- getLocalService(VirtualDeviceManagerInternal.class);
- flags |= localVdm.getBaseVirtualDisplayFlags(virtualDevice);
}
if (surface != null && surface.isSingleBuffered()) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 65a729a..d14f2a0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -587,7 +587,8 @@
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
logicalDisplay.getPowerThrottlingDataIdLocked(),
- mDisplayDeviceConfig), mContext, flags, mSensorManager);
+ mDisplayDeviceConfig,
+ mDisplayId), mContext, flags, mSensorManager);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -892,7 +893,8 @@
// will call updatePowerState if needed.
mBrightnessClamperController.onDisplayChanged(
new BrightnessClamperController.DisplayDeviceData(uniqueId,
- thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+ thermalBrightnessThrottlingDataId, powerThrottlingDataId,
+ config, mDisplayId));
if (changed) {
updatePowerState();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 101ad30..2206402 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -23,23 +23,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
import android.util.Slog;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
@@ -50,30 +44,22 @@
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
-import com.android.server.display.utils.AmbientFilter;
-import com.android.server.display.utils.AmbientFilterFactory;
-import com.android.server.display.utils.DebugUtils;
-import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
/**
* Clampers controller, all in DisplayControllerHandler
*/
public class BrightnessClamperController {
private static final String TAG = "BrightnessClamperController";
- // To enable these logs, run:
- // 'adb shell setprop persist.log.tag.BrightnessClamperController DEBUG && adb reboot'
- private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
- public static final float INVALID_LUX = -1f;
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
- private final SensorManager mSensorManager;
+ private final LightSensorController mLightSensorController;
+
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
@@ -85,70 +71,49 @@
private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
- private final SensorEventListener mLightSensorListener;
- private Sensor mRegisteredLightSensor = null;
- private Sensor mLightSensor;
- private String mLightSensorType;
- private String mLightSensorName;
- private AmbientFilter mAmbientFilter;
- private final DisplayDeviceConfig mDisplayDeviceConfig;
- private final Resources mResources;
- private final int mLightSensorRate;
- private final Injector mInjector;
private boolean mClamperApplied = false;
+ private final LightSensorController.LightSensorListener mLightSensorListener =
+ new LightSensorController.LightSensorListener() {
+ @Override
+ public void onAmbientLuxChange(float lux) {
+ mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux));
+ }
+ };
+
public BrightnessClamperController(Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags, SensorManager sensorManager) {
- this(null, handler, clamperChangeListener, data, context, flags, sensorManager);
+ this(new Injector(), handler, clamperChangeListener, data, context, flags, sensorManager);
}
@VisibleForTesting
BrightnessClamperController(Injector injector, Handler handler,
ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags, SensorManager sensorManager) {
- mInjector = injector == null ? new Injector() : injector;
- mDeviceConfigParameterProvider = mInjector.getDeviceConfigParameterProvider();
+ mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
- mSensorManager = sensorManager;
- mDisplayDeviceConfig = data.mDisplayDeviceConfig;
- mLightSensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- long now = SystemClock.elapsedRealtime();
- mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp),
- event.values[0]);
- final float lux = mAmbientFilter.getEstimate(now);
- mModifiers.forEach(mModifier -> mModifier.setAmbientLux(lux));
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // unused
- }
- };
+ mLightSensorController = injector.getLightSensorController(sensorManager, context,
+ mLightSensorListener, mHandler);
mClamperChangeListenerExternal = clamperChangeListener;
mExecutor = new HandlerExecutor(handler);
- mResources = context.getResources();
- mLightSensorRate = context.getResources().getInteger(
- R.integer.config_autoBrightnessLightSensorRate);
Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
-
ClamperChangeListener clamperChangeListenerInternal = () -> {
if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
mHandler.post(clamperChangeRunnableInternal);
}
};
- mClampers = mInjector.getClampers(handler, clamperChangeListenerInternal, data, flags,
+ mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
context);
- mModifiers = mInjector.getModifiers(flags, context, handler, clamperChangeListener,
- data.mDisplayDeviceConfig, mSensorManager);
+ mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
+ data.mDisplayDeviceConfig);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+ mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
start();
}
@@ -156,7 +121,9 @@
* Should be called when display changed. Forwards the call to individual clampers
*/
public void onDisplayChanged(DisplayDeviceData data) {
+ mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+ adjustLightSensorSubscription();
}
/**
@@ -184,9 +151,9 @@
}
if (displayState != STATE_ON) {
- unregisterSensorListener();
+ mLightSensorController.stop();
} else {
- maybeRegisterLightSensor();
+ adjustLightSensorSubscription();
}
for (int i = 0; i < mModifiers.size(); i++) {
@@ -231,9 +198,8 @@
writer.println(" mBrightnessCap: " + mBrightnessCap);
writer.println(" mClamperType: " + mClamperType);
writer.println(" mClamperApplied: " + mClamperApplied);
- writer.println(" mLightSensor=" + mLightSensor);
- writer.println(" mRegisteredLightSensor=" + mRegisteredLightSensor);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
+ mLightSensorController.dump(ipw);
mClampers.forEach(clamper -> clamper.dump(ipw));
mModifiers.forEach(modifier -> modifier.dump(ipw));
}
@@ -245,6 +211,7 @@
public void stop() {
mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
mOnPropertiesChangedListener);
+ mLightSensorController.stop();
mClampers.forEach(BrightnessClamper::stop);
mModifiers.forEach(BrightnessStateModifier::stop);
}
@@ -281,10 +248,15 @@
if (!mClampers.isEmpty()) {
mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
mExecutor, mOnPropertiesChangedListener);
- reloadLightSensorData(mDisplayDeviceConfig);
- mLightSensor = mInjector.getLightSensor(
- mSensorManager, mLightSensorType, mLightSensorName);
- maybeRegisterLightSensor();
+ }
+ adjustLightSensorSubscription();
+ }
+
+ private void adjustLightSensorSubscription() {
+ if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ mLightSensorController.restart();
+ } else {
+ mLightSensorController.stop();
}
}
@@ -323,7 +295,7 @@
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
+ DisplayDeviceConfig displayDeviceConfig) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
@@ -335,11 +307,12 @@
return modifiers;
}
- Sensor getLightSensor(SensorManager sensorManager, String type, String name) {
- return SensorUtils.findSensor(sensorManager, type,
- name, Sensor.TYPE_LIGHT);
+ LightSensorController getLightSensorController(SensorManager sensorManager,
+ Context context, LightSensorController.LightSensorListener listener,
+ Handler handler) {
+ return new LightSensorController(sensorManager, context.getResources(),
+ listener, handler);
}
-
}
/**
@@ -354,17 +327,21 @@
private final String mThermalThrottlingDataId;
@NonNull
private final String mPowerThrottlingDataId;
-
+ @NonNull
private final DisplayDeviceConfig mDisplayDeviceConfig;
+ private final int mDisplayId;
+
public DisplayDeviceData(@NonNull String uniqueDisplayId,
@NonNull String thermalThrottlingDataId,
@NonNull String powerThrottlingDataId,
- @NonNull DisplayDeviceConfig displayDeviceConfig) {
+ @NonNull DisplayDeviceConfig displayDeviceConfig,
+ int displayId) {
mUniqueDisplayId = uniqueDisplayId;
mThermalThrottlingDataId = thermalThrottlingDataId;
mPowerThrottlingDataId = powerThrottlingDataId;
mDisplayDeviceConfig = displayDeviceConfig;
+ mDisplayId = displayId;
}
@@ -412,55 +389,18 @@
}
@NonNull
+ @Override
public SensorData getTempSensor() {
return mDisplayDeviceConfig.getTempSensor();
}
- }
- private void maybeRegisterLightSensor() {
- if (mModifiers.stream().noneMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
- return;
+ @NonNull
+ SensorData getAmbientLightSensor() {
+ return mDisplayDeviceConfig.getAmbientLightSensor();
}
- if (mRegisteredLightSensor == mLightSensor) {
- return;
- }
-
- if (mRegisteredLightSensor != null) {
- unregisterSensorListener();
- }
-
- mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, mResources);
- mSensorManager.registerListener(mLightSensorListener,
- mLightSensor, mLightSensorRate * 1000, mHandler);
- mRegisteredLightSensor = mLightSensor;
-
- if (DEBUG) {
- Slog.d(TAG, "maybeRegisterLightSensor");
- }
- }
-
- private void unregisterSensorListener() {
- mSensorManager.unregisterListener(mLightSensorListener);
- mRegisteredLightSensor = null;
- mModifiers.forEach(mModifier -> mModifier.setAmbientLux(INVALID_LUX)); // set lux to invalid
- if (DEBUG) {
- Slog.d(TAG, "unregisterSensorListener");
- }
- }
-
- private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
- // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
- // it naturally falls back to the global config.xml.
- if (displayDeviceConfig != null
- && displayDeviceConfig.getAmbientLightSensor() != null) {
- // This covers both the ddc and the config.xml fallback
- mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
- mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
- } else if (mLightSensorName == null && mLightSensorType == null) {
- mLightSensorType = mResources.getString(
- com.android.internal.R.string.config_displayLightSensorType);
- mLightSensorName = "";
+ int getDisplayId() {
+ return mDisplayId;
}
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java
new file mode 100644
index 0000000..d89dd28
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/LightSensorController.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.config.SensorData;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.DebugUtils;
+import com.android.server.display.utils.SensorUtils;
+
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages light sensor subscription and notifies its listener about ambient lux changes
+ */
+public class LightSensorController {
+ private static final String TAG = "LightSensorController";
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.LightSensorController DEBUG && adb reboot'
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ static final float INVALID_LUX = -1f;
+
+ private final SensorManager mSensorManager;
+ private final LightSensorListener mLightSensorListener;
+ private final Handler mHandler;
+ private final Injector mInjector;
+ private final AmbientFilter mAmbientFilter;
+
+ private Sensor mLightSensor;
+ private Sensor mRegisteredLightSensor = null;
+ private final int mLightSensorRate;
+
+ private final SensorEventListener mLightSensorEventListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ long now = mInjector.getTime();
+ mAmbientFilter.addValue(TimeUnit.NANOSECONDS.toMillis(event.timestamp),
+ event.values[0]);
+ final float lux = mAmbientFilter.getEstimate(now);
+ mLightSensorListener.onAmbientLuxChange(lux);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // unused
+ }
+ };
+
+ LightSensorController(SensorManager sensorManager, Resources resources,
+ LightSensorListener listener, Handler handler) {
+ this(sensorManager, resources, listener, handler, new Injector());
+ }
+
+ @VisibleForTesting
+ LightSensorController(SensorManager sensorManager, Resources resources,
+ LightSensorListener listener, Handler handler, Injector injector) {
+ mSensorManager = sensorManager;
+ mLightSensorRate = injector.getLightSensorRate(resources);
+ mAmbientFilter = injector.getAmbientFilter(resources);
+ mLightSensorListener = listener;
+ mHandler = handler;
+ mInjector = injector;
+ }
+
+ void restart() {
+ if (mRegisteredLightSensor == mLightSensor) {
+ return;
+ }
+ if (mRegisteredLightSensor != null) {
+ stop();
+ }
+ if (mLightSensor == null) {
+ return;
+ }
+
+ mSensorManager.registerListener(mLightSensorEventListener,
+ mLightSensor, mLightSensorRate * 1000, mHandler);
+ mRegisteredLightSensor = mLightSensor;
+
+ if (DEBUG) {
+ Slog.d(TAG, "restart");
+ }
+ }
+
+ void stop() {
+ if (mRegisteredLightSensor == null) {
+ return;
+ }
+ mSensorManager.unregisterListener(mLightSensorEventListener);
+ mRegisteredLightSensor = null;
+ mAmbientFilter.clear();
+ mLightSensorListener.onAmbientLuxChange(INVALID_LUX);
+ if (DEBUG) {
+ Slog.d(TAG, "stop");
+ }
+ }
+
+ void configure(SensorData sensorData, int displayId) {
+ final int fallbackType = displayId == Display.DEFAULT_DISPLAY
+ ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
+ mLightSensor = mInjector.getLightSensor(mSensorManager, sensorData, fallbackType);
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println("LightSensorController");
+ writer.println(" mLightSensor=" + mLightSensor);
+ writer.println(" mRegisteredLightSensor=" + mRegisteredLightSensor);
+ }
+
+ static class Injector {
+ @Nullable
+ Sensor getLightSensor(SensorManager sensorManager, SensorData sensorData,
+ int fallbackType) {
+ return SensorUtils.findSensor(sensorManager, sensorData, fallbackType);
+ }
+
+ AmbientFilter getAmbientFilter(Resources resources) {
+ return AmbientFilterFactory.createBrightnessFilter(TAG, resources);
+ }
+
+ int getLightSensorRate(Resources resources) {
+ return resources.getInteger(R.integer.config_autoBrightnessLightSensorRate);
+ }
+
+ // should be consistent with SensorEvent.timestamp
+ long getTime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ interface LightSensorListener {
+ void onAmbientLuxChange(float ambientLux);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index 559b625..4764e4f 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -44,6 +44,32 @@
@Nullable
private static volatile ContentResolver sContentResolver = null;
+ private static volatile boolean sTestMode = false;
+
+ /**
+ * Can be called from unit tests to start the test mode, where a fake implementation will be
+ * used instead.
+ *
+ * <p>The fake implementation is just an {@link ArrayMap}. By default it is empty, and the data
+ * written can be read back later.</p>
+ */
+ @AnyThread
+ static void startTestMode() {
+ sTestMode = true;
+ }
+
+ /**
+ * Can be called from unit tests to end the test mode, where a fake implementation will be used
+ * instead.
+ */
+ @AnyThread
+ static void endTestMode() {
+ synchronized (sUserMap) {
+ sUserMap.clear();
+ }
+ sTestMode = false;
+ }
+
/**
* Not intended to be instantiated.
*/
@@ -78,6 +104,52 @@
int getInt(String key, int defaultValue);
}
+ private static class FakeReaderWriterImpl implements ReaderWriter {
+ @GuardedBy("mNonPersistentKeyValues")
+ private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>();
+
+ @AnyThread
+ @Override
+ public void putString(String key, String value) {
+ synchronized (mNonPersistentKeyValues) {
+ mNonPersistentKeyValues.put(key, value);
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ @Override
+ public String getString(String key, String defaultValue) {
+ synchronized (mNonPersistentKeyValues) {
+ if (mNonPersistentKeyValues.containsKey(key)) {
+ final String result = mNonPersistentKeyValues.get(key);
+ return result != null ? result : defaultValue;
+ }
+ return defaultValue;
+ }
+ }
+
+ @AnyThread
+ @Override
+ public void putInt(String key, int value) {
+ synchronized (mNonPersistentKeyValues) {
+ mNonPersistentKeyValues.put(key, String.valueOf(value));
+ }
+ }
+
+ @AnyThread
+ @Override
+ public int getInt(String key, int defaultValue) {
+ synchronized (mNonPersistentKeyValues) {
+ if (mNonPersistentKeyValues.containsKey(key)) {
+ final String result = mNonPersistentKeyValues.get(key);
+ return result != null ? Integer.parseInt(result) : defaultValue;
+ }
+ return defaultValue;
+ }
+ }
+ }
+
private static class UnlockedUserImpl implements ReaderWriter {
@UserIdInt
private final int mUserId;
@@ -200,6 +272,9 @@
private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal,
@UserIdInt int userId) {
+ if (sTestMode) {
+ return new FakeReaderWriterImpl();
+ }
return userManagerInternal.isUserUnlockingOrUnlocked(userId)
? new UnlockedUserImpl(userId, sContentResolver)
: new LockedUserImpl(userId, sContentResolver);
@@ -234,6 +309,9 @@
return readerWriter;
}
}
+ if (sTestMode) {
+ return putOrGet(userId, new FakeReaderWriterImpl());
+ }
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
if (!userManagerInternal.exists(userId)) {
@@ -276,6 +354,10 @@
*/
@AnyThread
static void onUserStarting(@UserIdInt int userId) {
+ if (sTestMode) {
+ putOrGet(userId, new FakeReaderWriterImpl());
+ return;
+ }
putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId));
}
@@ -286,6 +368,10 @@
*/
@AnyThread
static void onUserUnlocking(@UserIdInt int userId) {
+ if (sTestMode) {
+ putOrGet(userId, new FakeReaderWriterImpl());
+ return;
+ }
final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver);
putOrGet(userId, readerWriter);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 22b33dd..ae3a2afb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -254,9 +254,6 @@
private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
- private static final boolean FIX_UNLOCKED_DEVICE_REQUIRED_KEYS =
- android.security.Flags.fixUnlockedDeviceRequiredKeysV2();
-
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
// ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -670,7 +667,6 @@
mActivityManager = injector.getActivityManager();
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_STARTING);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
@@ -909,13 +905,7 @@
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
- if (!FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- // Notify keystore that a new user was added.
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- AndroidKeyStoreMaintenance.onUserAdded(userHandle);
- }
- } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
+ if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
mStorage.prefetchUser(userHandle);
} else if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
@@ -1130,32 +1120,14 @@
// Note: if this migration gets interrupted (e.g. by the device powering off), there
// shouldn't be a problem since this will run again on the next boot, and
// setCeStorageProtection() and initKeystoreSuperKeys(..., true) are idempotent.
- if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
- for (UserInfo user : mUserManager.getAliveUsers()) {
- removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
- synchronized (mSpManager) {
- migrateUserToSpWithBoundKeysLocked(user.id);
- }
+ if (!getBoolean(MIGRATED_SP_FULL, false, 0)) {
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
+ synchronized (mSpManager) {
+ migrateUserToSpWithBoundKeysLocked(user.id);
}
- setBoolean(MIGRATED_SP_FULL, true, 0);
}
- } else {
- if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
- for (UserInfo user : mUserManager.getAliveUsers()) {
- removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
- synchronized (mSpManager) {
- migrateUserToSpWithBoundCeKeyLocked(user.id);
- }
- }
- setString(MIGRATED_SP_CE_ONLY, "true", 0);
- }
-
- if (getBoolean(MIGRATED_SP_FULL, false, 0)) {
- // The FIX_UNLOCKED_DEVICE_REQUIRED_KEYS flag was enabled but then got disabled.
- // Ensure the full migration runs again the next time the flag is enabled...
- setBoolean(MIGRATED_SP_FULL, false, 0);
- }
+ setBoolean(MIGRATED_SP_FULL, true, 0);
}
mThirdPartyAppsStarted = true;
@@ -1163,30 +1135,6 @@
}
@GuardedBy("mSpManager")
- private void migrateUserToSpWithBoundCeKeyLocked(@UserIdInt int userId) {
- if (isUserSecure(userId)) {
- Slogf.d(TAG, "User %d is secured; no migration needed", userId);
- return;
- }
- long protectorId = getCurrentLskfBasedProtectorId(userId);
- if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
- initializeSyntheticPassword(userId);
- } else {
- Slogf.i(TAG, "Existing unsecured user %d has a synthetic password; re-encrypting CE " +
- "key with it", userId);
- AuthenticationResult result = mSpManager.unlockLskfBasedProtector(
- getGateKeeperService(), protectorId, LockscreenCredential.createNone(), userId,
- null);
- if (result.syntheticPassword == null) {
- Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
- return;
- }
- setCeStorageProtection(userId, result.syntheticPassword);
- }
- }
-
- @GuardedBy("mSpManager")
private void migrateUserToSpWithBoundKeysLocked(@UserIdInt int userId) {
if (isUserSecure(userId)) {
Slogf.d(TAG, "User %d is secured; no migration needed", userId);
@@ -1496,11 +1444,6 @@
}
@VisibleForTesting /** Note: this method is overridden in unit tests */
- void setKeystorePassword(byte[] password, int userHandle) {
- AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
- }
-
- @VisibleForTesting /** Note: this method is overridden in unit tests */
void initKeystoreSuperKeys(@UserIdInt int userId, SyntheticPassword sp, boolean allowExisting) {
final byte[] password = sp.deriveKeyStorePassword();
try {
@@ -2237,9 +2180,7 @@
return;
}
onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
- if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- unlockKeystore(userId, result.syntheticPassword);
- }
+ unlockKeystore(userId, result.syntheticPassword);
unlockCeStorage(userId, result.syntheticPassword);
}
}
@@ -2545,9 +2486,7 @@
// long time, so for now we keep doing it just in case it's ever important. Don't wait
// until initKeystoreSuperKeys() to do this; that can be delayed if the user is being
// created during early boot, and maybe something will use Keystore before then.
- if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- AndroidKeyStoreMaintenance.onUserAdded(userId);
- }
+ AndroidKeyStoreMaintenance.onUserAdded(userId);
synchronized (mUserCreationAndRemovalLock) {
// During early boot, don't actually create the synthetic password yet, but rather
@@ -2973,9 +2912,7 @@
LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
setCeStorageProtection(userId, sp);
- if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- initKeystoreSuperKeys(userId, sp, /* allowExisting= */ false);
- }
+ initKeystoreSuperKeys(userId, sp, /* allowExisting= */ false);
onSyntheticPasswordCreated(userId, sp);
Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
return sp;
@@ -3090,9 +3027,6 @@
if (!mSpManager.hasSidForUser(userId)) {
mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
- if (!FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- setKeystorePassword(sp.deriveKeyStorePassword(), userId);
- }
}
} else {
// Cache all profile password if they use unified challenge. This will later be used to
@@ -3103,11 +3037,7 @@
gateKeeperClearSecureUserId(userId);
unlockCeStorage(userId, sp);
unlockKeystore(userId, sp);
- if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) {
- AndroidKeyStoreMaintenance.onUserLskfRemoved(userId);
- } else {
- setKeystorePassword(null, userId);
- }
+ AndroidKeyStoreMaintenance.onUserLskfRemoved(userId);
removeBiometricsForUser(userId);
}
setCurrentLskfBasedProtectorId(newProtectorId, userId);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 3673eb0..56b93e8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -839,6 +839,13 @@
+ "Disallowed route: "
+ route);
}
+
+ if (route.isSystemRouteType()) {
+ throw new SecurityException(
+ "Only the system is allowed to publish routes with system route types. "
+ + "Disallowed route: "
+ + route);
+ }
}
Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
index 2facab7..a4460b2 100644
--- a/services/core/java/com/android/server/notification/TimeToLiveHelper.java
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -54,13 +54,17 @@
private final AlarmManager mAm;
@VisibleForTesting
+ @GuardedBy("mLock")
final TreeSet<Pair<Long, String>> mKeys;
+ final Object mLock = new Object();
public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) {
mContext = context;
mNm = nm;
mAm = context.getSystemService(AlarmManager.class);
- mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+ synchronized (mLock) {
+ mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+ }
IntentFilter timeoutFilter = new IntentFilter(ACTION);
timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
@@ -73,7 +77,9 @@
}
void dump(PrintWriter pw, String indent) {
- pw.println(indent + "mKeys " + mKeys);
+ synchronized (mLock) {
+ pw.println(indent + "mKeys " + mKeys);
+ }
}
private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) {
@@ -93,30 +99,35 @@
@VisibleForTesting
void scheduleTimeoutLocked(NotificationRecord record, long currentTime) {
- removeMatchingEntry(record.getKey());
+ synchronized (mLock) {
+ removeMatchingEntry(record.getKey());
- final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
- if (record.getNotification().getTimeoutAfter() > 0) {
- final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
+ final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
+ if (record.getNotification().getTimeoutAfter() > 0) {
+ final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
- // Maybe replace alarm with an earlier one
- if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
- if (currentEarliestTime != null) {
- cancelFirstAlarm();
+ // Maybe replace alarm with an earlier one
+ if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
+ if (currentEarliestTime != null) {
+ cancelFirstAlarm();
+ }
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ maybeScheduleFirstAlarm();
+ } else {
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
}
- mKeys.add(Pair.create(timeoutAfter, record.getKey()));
- maybeScheduleFirstAlarm();
- } else {
- mKeys.add(Pair.create(timeoutAfter, record.getKey()));
}
}
}
@VisibleForTesting
void cancelScheduledTimeoutLocked(NotificationRecord record) {
- removeMatchingEntry(record.getKey());
+ synchronized (mLock) {
+ removeMatchingEntry(record.getKey());
+ }
}
+ @GuardedBy("mLock")
private void removeMatchingEntry(String key) {
if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) {
// cancel the first alarm, remove the first entry, maybe schedule the alarm for the new
@@ -139,11 +150,13 @@
}
}
+ @GuardedBy("mLock")
private void cancelFirstAlarm() {
final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT);
mAm.cancel(pi);
}
+ @GuardedBy("mLock")
private void maybeScheduleFirstAlarm() {
if (!mKeys.isEmpty()) {
final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second,
@@ -162,13 +175,17 @@
return;
}
if (ACTION.equals(action)) {
- Pair<Long, String> earliest = mKeys.first();
- String key = intent.getStringExtra(EXTRA_KEY);
- if (!earliest.second.equals(key)) {
- Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+ String timeoutKey = null;
+ synchronized (mLock) {
+ Pair<Long, String> earliest = mKeys.first();
+ String key = intent.getStringExtra(EXTRA_KEY);
+ if (!earliest.second.equals(key)) {
+ Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+ }
+ removeMatchingEntry(key);
+ timeoutKey = earliest.second;
}
- removeMatchingEntry(key);
- mNm.timeoutNotification(earliest.second);
+ mNm.timeoutNotification(timeoutKey);
}
}
};
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 69490a8..5b4f310 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -126,10 +126,12 @@
userId, resolveForStart, /*allowDynamicSplits*/ true);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
- false /* isReceiver */, resolveForStart, filterCallingUid, callingPid);
- args.platformCompat = mPlatformCompat;
- SaferIntentUtils.filterNonExportedComponents(args, query);
+ if (resolveForStart) {
+ var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+ false /* isReceiver */, true, filterCallingUid, callingPid);
+ args.platformCompat = mPlatformCompat;
+ SaferIntentUtils.filterNonExportedComponents(args, query);
+ }
final boolean queryMayBeFiltered =
UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index db94d0e..d1d8993 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5918,6 +5918,7 @@
return userData;
}
+ /** For testing only! Directly, unnaturally removes userId from list of users. */
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 28254d0..46e6546 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -274,7 +274,9 @@
mVirtualDeviceManagerInternal =
LocalServices.getService(VirtualDeviceManagerInternal.class);
}
- return mVirtualDeviceManagerInternal.getPersistentIdForDevice(deviceId);
+ return mVirtualDeviceManagerInternal == null
+ ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ : mVirtualDeviceManagerInternal.getPersistentIdForDevice(deviceId);
}
@Override
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 3138a9e..ddbd809 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1026,12 +1026,7 @@
continue;
}
- final boolean trusted;
- if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
- trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
- } else {
- trusted = aggregateIsTrusted(id);
- }
+ final boolean trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
boolean showingKeyguard = true;
boolean biometricAuthenticated = false;
boolean currentUserIsUnlocked = false;
@@ -1092,19 +1087,15 @@
private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
if (isLocked) {
- if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
- // A profile with unified challenge is unlockable not by its own biometrics and
- // trust agents, but rather by those of the parent user. Therefore, when protecting
- // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
- // biometric SIDs and weak unlock methods, not the profile's.
- int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
- ? resolveProfileParent(userId) : userId;
+ // A profile with unified challenge is unlockable not by its own biometrics and
+ // trust agents, but rather by those of the parent user. Therefore, when protecting
+ // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+ // biometric SIDs and weak unlock methods, not the profile's.
+ int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+ ? resolveProfileParent(userId) : userId;
- mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId),
- isWeakUnlockMethodEnabled(authUserId));
- } else {
- mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(userId), false);
- }
+ mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+ isWeakUnlockMethodEnabled(authUserId));
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 84c37180..6537228 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -69,6 +69,7 @@
CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+ CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 3dcc7a6..7f60dc44 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -193,6 +193,27 @@
}
};
+ @VisibleForTesting
+ final AppOpsManager.OnOpChangedInternalListener mAppOpsChangeListener =
+ new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(int op, String packageName) {
+ if (op != AppOpsManager.OP_VIBRATE) {
+ return;
+ }
+ synchronized (mLock) {
+ if (shouldCancelAppOpModeChangedLocked(mNextVibration)) {
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_APP_OPS));
+ }
+ if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) {
+ mCurrentVibration.notifyCancelled(new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_APP_OPS), /* immediate= */ false);
+ }
+ }
+ }
+ };
+
static native long nativeInit(OnSyncedVibrationCompleteListener listener);
static native long nativeGetFinalizer();
@@ -238,6 +259,9 @@
mBatteryStatsService = injector.getBatteryStatsService();
mAppOps = mContext.getSystemService(AppOpsManager.class);
+ if (Flags.cancelByAppops()) {
+ mAppOps.startWatchingMode(AppOpsManager.OP_VIBRATE, null, mAppOpsChangeListener);
+ }
PowerManager pm = context.getSystemService(PowerManager.class);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
@@ -1390,6 +1414,15 @@
}
@GuardedBy("mLock")
+ private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) {
+ if (conductor == null) {
+ return false;
+ }
+ return checkAppOpModeLocked(conductor.getVibration().callerInfo)
+ != AppOpsManager.MODE_ALLOWED;
+ }
+
+ @GuardedBy("mLock")
private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
for (int i = 0; i < mVibrators.size(); i++) {
consumer.accept(mVibrators.valueAt(i));
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1ce324f..5699fdd 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -252,7 +252,8 @@
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevActivities.size() > 0) {
- if (!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) {
+ if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
+ && isAllActivitiesCreated(prevActivities)) {
// We have another Activity in the same currentTask to go to
final WindowContainer parent = currentActivity.getParent();
final boolean canCustomize = parent != null
@@ -549,6 +550,17 @@
return !prevActivities.isEmpty();
}
+ private static boolean isAllActivitiesCreated(
+ @NonNull ArrayList<ActivityRecord> prevActivities) {
+ for (int i = prevActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord check = prevActivities.get(i);
+ if (check.isState(ActivityRecord.State.INITIALIZING)) {
+ return false;
+ }
+ }
+ return !prevActivities.isEmpty();
+ }
+
boolean isMonitoringTransition() {
return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote();
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3b3eeb4..4a0239b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -41,7 +41,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.window.flags.Flags.windowSessionRelayoutInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -64,7 +63,6 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.MergedConfiguration;
import android.util.Slog;
import android.view.IWindow;
import android.view.IWindowId;
@@ -81,7 +79,6 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowRelayoutResult;
-import android.window.ClientWindowFrames;
import android.window.InputTransferToken;
import android.window.OnBackInvokedCallbackInfo;
@@ -290,37 +287,12 @@
return res;
}
- /** @deprecated */
- @Deprecated
- @Override
- public int relayoutLegacy(IWindow window, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
- int lastSyncSeqId, ClientWindowFrames outFrames,
- MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
- Bundle outBundle) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
- int res = mService.relayoutWindow(this, window, attrs,
- requestedWidth, requestedHeight, viewFlags, flags, seq,
- lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
- outActiveControls, outBundle);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return res;
- }
-
@Override
public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
int lastSyncSeqId) {
- if (windowSessionRelayoutInfo()) {
- relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
- lastSyncSeqId, null /* outRelayoutResult */);
- } else {
- relayoutLegacy(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
- lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
- null /* outSurfaceControl */, null /* outInsetsState */,
- null /* outActiveControls */, null /* outSyncIdBundle */);
- }
+ relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, null /* outRelayoutResult */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bc45c70..7e61023 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1903,7 +1903,10 @@
} else {
final List<TransitionInfo.Change> changes = info.getChanges();
for (int i = changes.size() - 1; i >= 0; --i) {
- if (mTargets.get(i).mContainer.asActivityRecord() != null) {
+ final WindowContainer<?> container = mTargets.get(i).mContainer;
+ if (container.asActivityRecord() != null
+ || (container.asTask() != null
+ && mOverrideOptions.getOverrideTaskTransition())) {
changes.get(i).setAnimationOptions(mOverrideOptions);
// TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2b375e1..72ec058 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2292,32 +2292,7 @@
outInsetsState = null;
outActiveControls = null;
}
- return relayoutWindowInner(session, client, attrs, requestedWidth, requestedHeight,
- viewVisibility, flags, seq, lastSyncSeqId, outFrames, outMergedConfiguration,
- outSurfaceControl, outInsetsState, outActiveControls, null /* outBundle */,
- outRelayoutResult);
- }
- /** @deprecated */
- @Deprecated
- public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
- int lastSyncSeqId, ClientWindowFrames outFrames,
- MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
- Bundle outBundle) {
- return relayoutWindowInner(session, client, attrs, requestedWidth, requestedHeight,
- viewVisibility, flags, seq, lastSyncSeqId, outFrames, outMergedConfiguration,
- outSurfaceControl, outInsetsState, outActiveControls, outBundle,
- null /* outRelayoutResult */);
- }
-
- private int relayoutWindowInner(Session session, IWindow client, LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
- int lastSyncSeqId, ClientWindowFrames outFrames,
- MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
- Bundle outBundle, WindowRelayoutResult outRelayoutResult) {
if (outActiveControls != null) {
outActiveControls.set(null, false /* copyControls */);
}
@@ -2649,14 +2624,8 @@
}
if (outFrames != null && outMergedConfiguration != null) {
- final boolean shouldReportActivityWindowInfo;
- if (Flags.windowSessionRelayoutInfo()) {
- shouldReportActivityWindowInfo = outRelayoutResult != null
+ final boolean shouldReportActivityWindowInfo = outRelayoutResult != null
&& win.mLastReportedActivityWindowInfo != null;
- } else {
- shouldReportActivityWindowInfo = outBundle != null
- && win.mLastReportedActivityWindowInfo != null;
- }
final ActivityWindowInfo outActivityWindowInfo = shouldReportActivityWindowInfo
? new ActivityWindowInfo()
: null;
@@ -2665,13 +2634,7 @@
outActivityWindowInfo, false /* useLatestConfig */, shouldRelayout);
if (shouldReportActivityWindowInfo) {
- if (Flags.windowSessionRelayoutInfo()) {
- outRelayoutResult.activityWindowInfo = outActivityWindowInfo;
- } else {
- outBundle.putParcelable(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO,
- outActivityWindowInfo);
- }
+ outRelayoutResult.activityWindowInfo = outActivityWindowInfo;
}
// Set resize-handled here because the values are sent back to the client.
@@ -2702,28 +2665,16 @@
win.isVisible() /* visible */, false /* removed */);
}
- if (Flags.windowSessionRelayoutInfo()) {
- if (outRelayoutResult != null) {
- if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
- && win.mSyncSeqId > lastSyncSeqId) {
- outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers()
- ? win.mSyncSeqId
- : -1;
- win.markRedrawForSyncReported();
- } else {
- outRelayoutResult.syncSeqId = -1;
- }
- }
- } else if (outBundle != null) {
- final int maybeSyncSeqId;
+ if (outRelayoutResult != null) {
if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
&& win.mSyncSeqId > lastSyncSeqId) {
- maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
+ outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers()
+ ? win.mSyncSeqId
+ : -1;
win.markRedrawForSyncReported();
} else {
- maybeSyncSeqId = -1;
+ outRelayoutResult.syncSeqId = -1;
}
- outBundle.putInt(IWindowSession.KEY_RELAYOUT_BUNDLE_SEQID, maybeSyncSeqId);
}
if (configChanged) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 0da17e1..3bce9b5 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -84,7 +84,6 @@
],
srcs: [
"src/com/android/server/inputmethod/**/ClientControllerTest.java",
- "src/com/android/server/inputmethod/**/UserDataRepositoryTest.java",
],
auto_gen_config: true,
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index c3a87da..79943f6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -30,6 +30,7 @@
import com.android.server.pm.UserManagerInternal;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,6 +64,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ SecureSettingsWrapper.startTestMode();
mHandler = new Handler(Looper.getMainLooper());
mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
@@ -73,6 +75,11 @@
};
}
+ @After
+ public void tearDown() {
+ SecureSettingsWrapper.endTestMode();
+ }
+
@Test
public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 69043f5..e982153 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -16,19 +16,19 @@
package com.android.server.display.brightness.clamper;
+import static android.view.Display.STATE_OFF;
import static android.view.Display.STATE_ON;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
@@ -40,12 +40,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.TestUtils;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.OffsettableClock;
@@ -63,6 +61,7 @@
@SmallTest
public class BrightnessClamperControllerTest {
private static final float FLOAT_TOLERANCE = 0.001f;
+ private static final int DISPLAY_ID = 2;
private final OffsettableClock mClock = new OffsettableClock();
private final TestHandler mTestHandler = new TestHandler(null, mClock);
@@ -78,8 +77,12 @@
@Mock
private BrightnessClamperController.DisplayDeviceData mMockDisplayDeviceData;
@Mock
+ private SensorData mMockSensorData;
+ @Mock
private DeviceConfigParameterProvider mMockDeviceConfigParameterProvider;
@Mock
+ private LightSensorController mMockLightSensorController;
+ @Mock
private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
@Mock
private DisplayManagerFlags mFlags;
@@ -88,23 +91,17 @@
@Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
- Sensor mLightSensor;
-
@Mock
private DeviceConfig.Properties mMockProperties;
private BrightnessClamperController mClamperController;
private TestInjector mTestInjector;
- @Rule
- public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier));
- when(mSensorManager.getDefaultSensor(anyInt())).thenReturn(mLightSensor);
- when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
+ when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
mClamperController = createBrightnessClamperController();
}
@@ -115,6 +112,25 @@
}
@Test
+ public void testConstructor_ConfiguresLightSensorController() {
+ verify(mMockLightSensorController).configure(mMockSensorData, DISPLAY_ID);
+ }
+
+ @Test
+ public void testConstructor_doesNotStartsLightSensorController() {
+ verify(mMockLightSensorController, never()).restart();
+ }
+
+ @Test
+ public void testConstructor_startsLightSensorController() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+
+ mClamperController = createBrightnessClamperController();
+
+ verify(mMockLightSensorController).restart();
+ }
+
+ @Test
public void testStop_RemovesOnPropertiesChangeListener() {
ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> captor = ArgumentCaptor.forClass(
DeviceConfig.OnPropertiesChangedListener.class);
@@ -152,6 +168,21 @@
}
@Test
+ public void testOnDisplayChanged_doesNotRestartLightSensor() {
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController, never()).restart();
+ }
+
+ @Test
+ public void testOnDisplayChanged_restartsLightSensor() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController).restart();
+ }
+
+ @Test
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
@@ -161,6 +192,26 @@
}
@Test
+ public void testClamp_restartsLightSensor() {
+ float initialBrightness = 0.2f;
+ boolean initialSlowChange = true;
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+
+ verify(mMockLightSensorController).restart();
+ }
+
+ @Test
+ public void testClamp_stopsLightSensor() {
+ float initialBrightness = 0.2f;
+ boolean initialSlowChange = true;
+ clearInvocations(mMockLightSensorController);
+ mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_OFF);
+
+ verify(mMockLightSensorController).stop();
+ }
+
+ @Test
public void testClamp_inactiveClamperNotApplied() {
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
@@ -260,33 +311,17 @@
}
@Test
- public void testAmbientLuxChanges() throws Exception {
- ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(
- SensorEventListener.class);
+ public void testAmbientLuxChanges() {
+ mTestInjector.mCapturedLightSensorListener.onAmbientLuxChange(50);
- verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
- anyInt(), any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
-
- when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))).thenReturn(List.of(mLightSensor));
-
- float initialBrightness = 0.8f;
- boolean initialSlowChange = true;
-
- DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
- initialSlowChange, STATE_ON);
- assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
-
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 50, mClock.now()));
verify(mMockModifier).setAmbientLux(50);
-
- listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 300, mClock.now()));
- verify(mMockModifier).setAmbientLux(300);
}
@Test
public void testStop() {
+ clearInvocations(mMockLightSensorController);
mClamperController.stop();
+ verify(mMockLightSensorController).stop();
verify(mMockModifier).stop();
verify(mMockClamper).stop();
}
@@ -303,6 +338,7 @@
private final List<BrightnessStateModifier> mModifiers;
private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener;
+ private LightSensorController.LightSensorListener mCapturedLightSensorListener;
private TestInjector(
List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
@@ -330,8 +366,15 @@
@Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
Handler handler, BrightnessClamperController.ClamperChangeListener listener,
- DisplayDeviceConfig displayDeviceConfig, SensorManager sensorManager) {
+ DisplayDeviceConfig displayDeviceConfig) {
return mModifiers;
}
+
+ @Override
+ LightSensorController getLightSensorController(SensorManager sensorManager, Context context,
+ LightSensorController.LightSensorListener listener, Handler handler) {
+ mCapturedLightSensorListener = listener;
+ return mMockLightSensorController;
+ }
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
new file mode 100644
index 0000000..b742d02
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper
+
+import android.content.res.Resources
+import android.hardware.Sensor
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.os.Handler
+import androidx.test.filters.SmallTest
+import com.android.server.display.TestUtils
+import com.android.server.display.brightness.clamper.LightSensorController.Injector
+import com.android.server.display.brightness.clamper.LightSensorController.LightSensorListener
+import com.android.server.display.config.SensorData
+import com.android.server.display.utils.AmbientFilter
+import org.junit.Before
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.inOrder
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
+
+private const val LIGHT_SENSOR_RATE: Int = 10
+private const val DISPLAY_ID: Int = 3
+private const val NOW: Long = 3_000
+
+@SmallTest
+class LightSensorControllerTest {
+
+ private val mockSensorManager: SensorManager = mock()
+ private val mockResources: Resources = mock()
+ private val mockLightSensorListener: LightSensorListener = mock()
+ private val mockHandler: Handler = mock()
+ private val mockAmbientFilter: AmbientFilter = mock()
+
+ private val testInjector = TestInjector()
+ private val dummySensorData = SensorData()
+
+ private lateinit var controller: LightSensorController
+
+ @Before
+ fun setUp() {
+ controller = LightSensorController(mockSensorManager, mockResources,
+ mockLightSensorListener, mockHandler, testInjector)
+ }
+
+ fun `does not register light sensor if is not configured`() {
+ controller.restart()
+
+ verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
+ }
+
+ fun `does not register light sensor if missing`() {
+ controller.configure(dummySensorData, DISPLAY_ID)
+ controller.restart()
+
+ verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
+ }
+
+ fun `register light sensor if configured and present`() {
+ testInjector.lightSensor = TestUtils
+ .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
+ controller.configure(dummySensorData, DISPLAY_ID)
+ controller.restart()
+
+ verify(mockSensorManager).registerListener(any(),
+ testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
+ }
+
+ fun `register light sensor once if not changed`() {
+ testInjector.lightSensor = TestUtils
+ .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
+ controller.configure(dummySensorData, DISPLAY_ID)
+
+ controller.restart()
+ controller.restart()
+
+ verify(mockSensorManager).registerListener(any(),
+ testInjector.lightSensor, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
+ }
+
+ fun `register new light sensor and unregister old if changed`() {
+ val lightSensor1 = TestUtils
+ .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
+ testInjector.lightSensor = lightSensor1
+ controller.configure(dummySensorData, DISPLAY_ID)
+ controller.restart()
+
+ val lightSensor2 = TestUtils
+ .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
+ testInjector.lightSensor = lightSensor2
+ controller.configure(dummySensorData, DISPLAY_ID)
+ controller.restart()
+
+ inOrder {
+ verify(mockSensorManager).registerListener(any(),
+ lightSensor1, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ verify(mockSensorManager).unregisterListener(any<SensorEventListener>())
+ verify(mockAmbientFilter).clear()
+ verify(mockLightSensorListener).onAmbientLuxChange(LightSensorController.INVALID_LUX)
+ verify(mockSensorManager).registerListener(any(),
+ lightSensor2, LIGHT_SENSOR_RATE * 1000, mockHandler)
+ }
+ verifyNoMoreInteractions(mockSensorManager, mockAmbientFilter, mockLightSensorListener)
+ }
+
+ fun `notifies listener on ambient lux change`() {
+ val expectedLux = 40f
+ val eventLux = 50
+ val eventTime = 60L
+ whenever(mockAmbientFilter.getEstimate(NOW)).thenReturn(expectedLux)
+ val listenerCaptor = argumentCaptor<SensorEventListener>()
+ testInjector.lightSensor = TestUtils
+ .createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT)
+ controller.configure(dummySensorData, DISPLAY_ID)
+ controller.restart()
+ verify(mockSensorManager).registerListener(listenerCaptor.capture(),
+ eq(testInjector.lightSensor), eq(LIGHT_SENSOR_RATE * 1000), eq(mockHandler))
+
+ val listener = listenerCaptor.lastValue
+ listener.onSensorChanged(TestUtils.createSensorEvent(testInjector.lightSensor,
+ eventLux, eventTime * 1_000_000))
+
+ inOrder {
+ verify(mockAmbientFilter).addValue(eventTime, eventLux.toFloat())
+ verify(mockLightSensorListener).onAmbientLuxChange(expectedLux)
+ }
+ }
+
+ private inner class TestInjector : Injector() {
+ var lightSensor: Sensor? = null
+ override fun getLightSensor(sensorManager: SensorManager?,
+ sensorData: SensorData?, fallbackType: Int): Sensor? {
+ return lightSensor
+ }
+
+ override fun getLightSensorRate(resources: Resources?): Int {
+ return LIGHT_SENSOR_RATE
+ }
+
+ override fun getAmbientFilter(resources: Resources?): AmbientFilter {
+ return mockAmbientFilter
+ }
+
+ override fun getTime(): Long {
+ return NOW
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index 3d03bf2..e2b93ae 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -205,7 +205,7 @@
@Override
public DreamOverlayConnectionHandler createOverlayConnection(
- ComponentName overlayComponent) {
+ ComponentName overlayComponent, Runnable onDisconnected) {
return mDreamOverlayConnectionHandler;
}
diff --git a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java
index 22d7e73..3e65585 100644
--- a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java
+++ b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java
@@ -49,10 +49,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DreamOverlayConnectionHandlerTest {
- private static final int MIN_CONNECTION_DURATION_MS = 100;
- private static final int MAX_RECONNECT_ATTEMPTS = 3;
- private static final int BASE_RECONNECT_DELAY_MS = 50;
-
@Mock
private Context mContext;
@Mock
@@ -63,6 +59,8 @@
private IDreamOverlay mOverlayService;
@Mock
private IDreamOverlayClient mOverlayClient;
+ @Mock
+ private Runnable mOnDisconnectRunnable;
private TestLooper mTestLooper;
private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler;
@@ -75,9 +73,7 @@
mContext,
mTestLooper.getLooper(),
mServiceIntent,
- MIN_CONNECTION_DURATION_MS,
- MAX_RECONNECT_ATTEMPTS,
- BASE_RECONNECT_DELAY_MS,
+ mOnDisconnectRunnable,
new TestInjector(mConnection));
}
@@ -119,12 +115,14 @@
mTestLooper.dispatchAll();
// No client yet, so we shouldn't have executed
verify(consumer, never()).accept(mOverlayClient);
+ verify(mOnDisconnectRunnable, never()).run();
provideClient();
// Service disconnected before looper could handle the message.
disconnectService();
mTestLooper.dispatchAll();
verify(consumer, never()).accept(mOverlayClient);
+ verify(mOnDisconnectRunnable).run();
}
@Test
@@ -237,8 +235,7 @@
@Override
public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context,
- Handler handler, Intent serviceIntent, int minConnectionDurationMs,
- int maxReconnectAttempts, int baseReconnectDelayMs) {
+ Handler handler, Intent serviceIntent) {
return mConnection;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
index 75e8e68..72883e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -134,6 +134,18 @@
}
@Test
+ public void testOnUserStarting_userIsRemovedFromTheStore() {
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+ assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ mUserWakeupStore.onUserStarting(USER_ID_3);
+ // getWakeupTimeForUser returns negative wakeup time if there is no entry for user.
+ assertEquals(-1, mUserWakeupStore.getWakeupTimeForUser(USER_ID_3));
+ assertEquals(2, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ }
+
+ @Test
public void testGetNextUserWakeup() {
mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 3_000);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 2a67029..7aec42b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -72,9 +72,6 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
@@ -124,9 +121,6 @@
.build();
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
@@ -418,7 +412,6 @@
// user, not the profile. This matches the authentication that is needed to unlock the device
// for the profile again.
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testLockDeviceForManagedProfileWithUnifiedChallenge_usesParentBiometricSids()
throws Exception {
setupMocksForProfile(/* unifiedChallenge= */ true);
@@ -617,7 +610,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -626,7 +618,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_WEAK);
@@ -634,7 +625,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -643,7 +633,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -652,7 +641,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
setupFace(SensorProperties.STRENGTH_WEAK);
@@ -660,7 +648,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
/* isNonStrongBiometricAllowed= */ false);
@@ -669,7 +656,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -678,7 +664,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -687,7 +672,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void
testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
throws Exception {
@@ -699,7 +683,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
throws Exception {
setupStrongAuthTrackerToAllowEverything();
@@ -710,7 +693,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFingerprint(SensorProperties.STRENGTH_STRONG);
@@ -718,7 +700,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
setupFace(SensorProperties.STRENGTH_STRONG);
@@ -726,7 +707,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
setupStrongAuthTrackerToAllowEverything();
verifyWeakUnlockDisabled();
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 30e3b18..dbab54b 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -672,6 +672,61 @@
new HashSet<>(mUserController.getRunningUsersLU()));
}
+ /** Test scheduling stopping of background users - reschedule if current user is a guest. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_rescheduleWhenGuest() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ final int TEST_USER_GUEST = 902;
+ setUpUser(TEST_USER_GUEST, UserInfo.FLAG_GUEST);
+
+ setUpUser(TEST_USER_ID2, NO_USERINFO_FLAGS);
+
+ // Switch to TEST_USER_ID from user 0
+ int numberOfUserSwitches = 0;
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+ ++numberOfUserSwitches, false,
+ /* expectScheduleBackgroundUserStopping= */ false);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Switch to TEST_USER_GUEST from TEST_USER_ID
+ addForegroundUserAndContinueUserSwitch(TEST_USER_GUEST, TEST_USER_ID,
+ ++numberOfUserSwitches, false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_GUEST),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete.
+ // TEST_USER_ID may be scheduled for stopping, but it shouldn't actually stop since the
+ // current user is a Guest.
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_GUEST);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_GUEST),
+ mUserController.getRunningUsersLU());
+
+ // Switch to TEST_USER_ID2 from TEST_USER_GUEST
+ // Guests are automatically stopped in the background, so it won't be scheduled.
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_GUEST,
+ ++numberOfUserSwitches, true,
+ /* expectScheduleBackgroundUserStopping= */ false);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID2),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete.
+ // TEST_USER_ID should *still* be scheduled for stopping, since we skipped stopping it
+ // earlier.
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_GUEST);
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID2),
+ mUserController.getRunningUsersLU());
+ }
+
/**
* Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
* @param userId the user we are checking to see whether it is scheduled.
@@ -682,11 +737,11 @@
boolean expectScheduled, @Nullable Integer userId) {
TestHandler handler = mInjector.mHandler;
if (expectScheduled) {
- assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ assertTrue(handler.hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId);
mUserController.processScheduledStopOfBackgroundUser(userId);
} else {
- assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ assertFalse(handler.hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
}
}
@@ -1534,9 +1589,9 @@
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
- assertEquals(mInjector.mHandler
- .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId),
- expectScheduleBackgroundUserStopping);
+ assertEquals(expectScheduleBackgroundUserStopping,
+ mInjector.mHandler
+ .hasEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId));
verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping,
expectScheduleBackgroundUserStopping);
@@ -1810,6 +1865,13 @@
}
private static class TestHandler extends Handler {
+ /**
+ * Keeps an accessible copy of messages that were queued for us to query.
+ *
+ * WARNING: queued messages get added to this, but processed/removed messages to NOT
+ * automatically get removed. This can lead to confusing bugs. Maybe one day someone will
+ * fix this, but in the meantime, this is your warning.
+ */
private final List<Message> mMessages = new ArrayList<>();
TestHandler(Looper looper) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index f9077c4..93fc071a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -196,11 +196,6 @@
}
@Override
- void setKeystorePassword(byte[] password, int userHandle) {
-
- }
-
- @Override
void initKeystoreSuperKeys(int userId, SyntheticPassword sp, boolean allowExisting) {
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index d6c0fef..1875284 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -781,6 +781,34 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS)
+ public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ var vib = vibrate(service,
+ VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), RINGTONE_ATTRS);
+
+ assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+ when(mAppOpsManagerMock.checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString()))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+
+ service.mAppOpsChangeListener.onOpChanged(AppOpsManager.OP_VIBRATE, null);
+
+ assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS));
+
+ var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(statsInfoCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0);
+ assertEquals(Vibration.Status.CANCELLED_BY_APP_OPS.getProtoEnumValue(),
+ touchMetrics.status);
+ }
+
+ @Test
public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
VibratorManagerService service = createSystemReadyService();
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 c67d1ec..8b4d779 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -30,6 +30,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -245,6 +246,7 @@
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
// reset drawing status
+ testCase.recordBack.setState(STOPPED, "stopped");
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
@@ -937,6 +939,7 @@
testCase.recordFront = record2;
testCase.windowBack = window1;
testCase.windowFront = window2;
+ record1.setState(STOPPED, "stopped");
return testCase;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 24ebad6..fcf7a3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -78,7 +78,6 @@
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.InputConfig;
import android.os.Process;
@@ -94,7 +93,6 @@
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
-import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -265,69 +263,7 @@
}
@Test
- public void testRelayoutExitingWindow_legacy() {
- mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
-
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
- win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
- spyOn(win.mTransitionController);
- doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
- doReturn(true).when(win.mTransitionController).inTransition(
- eq(win.mActivityRecord));
- win.mViewVisibility = View.VISIBLE;
- win.mHasSurface = true;
- win.mActivityRecord.mAppStopped = true;
- mWm.mWindowMap.put(win.mClient.asBinder(), win);
- spyOn(mWm.mWindowPlacerLocked);
- // Skip unnecessary operations of relayout.
- doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
- final int w = 100;
- final int h = 200;
- final ClientWindowFrames outFrames = new ClientWindowFrames();
- final MergedConfiguration outConfig = new MergedConfiguration();
- final SurfaceControl outSurfaceControl = new SurfaceControl();
- final InsetsState outInsetsState = new InsetsState();
- final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array();
- final Bundle outBundle = new Bundle();
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
- // The window is in transition, so its destruction is deferred.
- assertTrue(win.mAnimatingExit);
- assertFalse(win.mDestroying);
- assertTrue(win.mTransitionController.mAnimatingExitWindows.contains(win));
-
- win.mAnimatingExit = false;
- win.mViewVisibility = View.VISIBLE;
- win.mActivityRecord.setVisibleRequested(false);
- win.mActivityRecord.setVisible(false);
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
- // Because the window is already invisible, it doesn't need to apply exiting animation
- // and WMS#tryStartExitingAnimation() will destroy the surface directly.
- assertFalse(win.mAnimatingExit);
- assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
-
- // Invisible requested activity should not get the last config even if its view is visible.
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
- assertEquals(0, outConfig.getMergedConfiguration().densityDpi);
- // Non activity window can still get the last config.
- win.mActivityRecord = null;
- win.fillClientWindowFramesAndConfiguration(outFrames, outConfig,
- null /* outActivityWindowInfo*/, false /* useLatestConfig */,
- true /* relayoutVisible */);
- assertEquals(win.getConfiguration().densityDpi,
- outConfig.getMergedConfiguration().densityDpi);
- }
-
- @Test
public void testRelayoutExitingWindow() {
- mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
-
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
win.mWinAnimator.mSurfaceController = surfaceController;
@@ -483,15 +419,8 @@
win.mRelayoutSeq = 1;
seq = 2;
}
- if (Flags.windowSessionRelayoutInfo()) {
- mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq,
- 0, new WindowRelayoutResult());
- } else {
- mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq,
- 0, new ClientWindowFrames(), new MergedConfiguration(),
- new SurfaceControl(), new InsetsState(), new InsetsSourceControl.Array(),
- new Bundle());
- }
+ mWm.relayoutWindow(win.mSession, win.mClient, newParams, 100, 200, View.VISIBLE, 0, seq,
+ 0, new WindowRelayoutResult());
ArgumentCaptor<Integer> changedFlags = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> changedPrivateFlags = ArgumentCaptor.forClass(Integer.class);
@@ -1364,70 +1293,8 @@
}
@Test
- public void testRelayout_appWindowSendActivityWindowInfo_legacy() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
- mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
-
- // Skip unnecessary operations of relayout.
- spyOn(mWm.mWindowPlacerLocked);
- doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
-
- final Task task = createTask(mDisplayContent);
- final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "appWindow");
- mWm.mWindowMap.put(win.mClient.asBinder(), win);
-
- final int w = 100;
- final int h = 200;
- final ClientWindowFrames outFrames = new ClientWindowFrames();
- final MergedConfiguration outConfig = new MergedConfiguration();
- final SurfaceControl outSurfaceControl = new SurfaceControl();
- final InsetsState outInsetsState = new InsetsState();
- final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array();
- final Bundle outBundle = new Bundle();
-
- final ActivityRecord activity = win.mActivityRecord;
- final ActivityWindowInfo expectedInfo = new ActivityWindowInfo();
- expectedInfo.set(true, new Rect(0, 0, 1000, 2000), new Rect(0, 0, 500, 2000));
- doReturn(expectedInfo).when(activity).getActivityWindowInfo();
- activity.setVisibleRequested(false);
- activity.setVisible(false);
-
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
-
- // No latest reported value, so return empty when activity is invisible
- final ActivityWindowInfo activityWindowInfo = outBundle.getParcelable(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class);
- assertEquals(new ActivityWindowInfo(), activityWindowInfo);
-
- activity.setVisibleRequested(true);
- activity.setVisible(true);
-
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
-
- // Report the latest when activity is visible.
- final ActivityWindowInfo activityWindowInfo2 = outBundle.getParcelable(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class);
- assertEquals(expectedInfo, activityWindowInfo2);
-
- expectedInfo.set(false, new Rect(0, 0, 1000, 2000), new Rect(0, 0, 1000, 2000));
- activity.setVisibleRequested(false);
- activity.setVisible(false);
-
- mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
-
- // Report the last reported value when activity is invisible.
- final ActivityWindowInfo activityWindowInfo3 = outBundle.getParcelable(
- IWindowSession.KEY_RELAYOUT_BUNDLE_ACTIVITY_WINDOW_INFO, ActivityWindowInfo.class);
- assertEquals(activityWindowInfo2, activityWindowInfo3);
- }
-
- @Test
public void testRelayout_appWindowSendActivityWindowInfo() {
mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
- mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b152c3e..e13376b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -86,7 +86,6 @@
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.InputConfig;
import android.os.RemoteException;
@@ -114,7 +113,6 @@
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Test;
@@ -1369,67 +1367,7 @@
@SetupWindows(addWindows = {W_INPUT_METHOD})
@Test
- public void testImeTargetChangeListener_OnImeTargetOverlayVisibilityChanged_legacy() {
- mSetFlagsRule.disableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
-
- final TestImeTargetChangeListener listener = new TestImeTargetChangeListener();
- mWm.mImeTargetChangeListener = listener;
-
- // Scenario 1: test addWindow/relayoutWindow to add Ime layering overlay window as visible.
- final WindowToken windowToken = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
- mDisplayContent);
- final IWindow client = new TestIWindow();
- final Session session = getTestSession();
- final ClientWindowFrames outFrames = new ClientWindowFrames();
- final MergedConfiguration outConfig = new MergedConfiguration();
- final SurfaceControl outSurfaceControl = new SurfaceControl();
- final InsetsState outInsetsState = new InsetsState();
- final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array();
- final Bundle outBundle = new Bundle();
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- TYPE_APPLICATION_OVERLAY);
- params.setTitle("imeLayeringTargetOverlay");
- params.token = windowToken.token;
- params.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
-
- mWm.addWindow(session, client, params, View.VISIBLE, DEFAULT_DISPLAY,
- 0 /* userUd */, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
- new InsetsSourceControl.Array(), new Rect(), new float[1]);
- mWm.relayoutWindow(session, client, params, 100, 200, View.VISIBLE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
- waitHandlerIdle(mWm.mH);
-
- final WindowState imeLayeringTargetOverlay = mDisplayContent.getWindow(
- w -> w.mClient.asBinder() == client.asBinder());
- assertThat(imeLayeringTargetOverlay.isVisible()).isTrue();
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isTrue();
-
- // Scenario 2: test relayoutWindow to let the Ime layering target overlay window invisible.
- mWm.relayoutWindow(session, client, params, 100, 200, View.GONE, 0, 0, 0,
- outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
- waitHandlerIdle(mWm.mH);
-
- assertThat(imeLayeringTargetOverlay.isVisible()).isFalse();
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
-
- // Scenario 3: test removeWindow to remove the Ime layering target overlay window.
- mWm.removeClientToken(session, client.asBinder());
- waitHandlerIdle(mWm.mH);
-
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isTrue();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
- }
-
- @SetupWindows(addWindows = {W_INPUT_METHOD})
- @Test
public void testImeTargetChangeListener_OnImeTargetOverlayVisibilityChanged() {
- mSetFlagsRule.enableFlags(Flags.FLAG_WINDOW_SESSION_RELAYOUT_INFO);
-
final TestImeTargetChangeListener listener = new TestImeTargetChangeListener();
mWm.mImeTargetChangeListener = listener;
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
index eadb726..2b515c9 100644
--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -64,6 +64,14 @@
* @hide
*/
public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY;
+ /**
+ * @hide
+ */
+ public static final int MAX_STRING_LENGTH = 256;
+ /**
+ * @hide
+ */
+ public static final int MAX_LIST_SIZE = 100;
/**
* Builder class for {@link VisualVoicemailSmsFilterSettings} objects.
@@ -82,11 +90,16 @@
/**
* Sets the client prefix for the visual voicemail SMS filter. The client prefix will appear
* at the start of a visual voicemail SMS message, followed by a colon(:).
+ * @throws IllegalArgumentException if the string length is greater than 256 characters
*/
public Builder setClientPrefix(String clientPrefix) {
if (clientPrefix == null) {
throw new IllegalArgumentException("Client prefix cannot be null");
}
+ if (clientPrefix.length() > MAX_STRING_LENGTH) {
+ throw new IllegalArgumentException("Client prefix cannot be greater than "
+ + MAX_STRING_LENGTH + " characters");
+ }
mClientPrefix = clientPrefix;
return this;
}
@@ -95,11 +108,25 @@
* Sets the originating number allow list for the visual voicemail SMS filter. If the list
* is not null only the SMS messages from a number in the list can be considered as a visual
* voicemail SMS. Otherwise, messages from any address will be considered.
+ * @throws IllegalArgumentException if the size of the originatingNumbers list is greater
+ * than 100 elements
+ * @throws IllegalArgumentException if an element within the originatingNumbers list has
+ * a string length greater than 256
*/
public Builder setOriginatingNumbers(List<String> originatingNumbers) {
if (originatingNumbers == null) {
throw new IllegalArgumentException("Originating numbers cannot be null");
}
+ if (originatingNumbers.size() > MAX_LIST_SIZE) {
+ throw new IllegalArgumentException("The originatingNumbers list size cannot be"
+ + " greater than " + MAX_STRING_LENGTH + " elements");
+ }
+ for (String num : originatingNumbers) {
+ if (num != null && num.length() > MAX_STRING_LENGTH) {
+ throw new IllegalArgumentException("Numbers within the originatingNumbers list"
+ + " cannot be greater than" + MAX_STRING_LENGTH + " characters");
+ }
+ }
mOriginatingNumbers = originatingNumbers;
return this;
}
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index d0e5626..0c3c7e2 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -17,9 +17,6 @@
package android.trust.test
import android.content.pm.PackageManager
-import android.platform.test.annotations.RequiresFlagsDisabled
-import android.platform.test.annotations.RequiresFlagsEnabled
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.service.trust.GrantTrustResult
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
@@ -58,7 +55,6 @@
.around(ScreenLockRule())
.around(lockStateTrackingRule)
.around(trustAgentRule)
- .around(DeviceFlagsValueProvider.createCheckFlagsRule())
@Before
fun manageTrust() {
@@ -93,7 +89,6 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
fun grantCannotActivelyUnlockDevice() {
// On automotive, trust agents can actively unlock the device.
assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
@@ -120,24 +115,6 @@
}
@Test
- @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
- fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
- // On automotive, trust agents can actively unlock the device.
- assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
-
- // Verify that b/296464083 exists. That is, when the device is locked
- // and a trust agent grants trust, the deviceLocked state incorrectly
- // becomes false even though the device correctly remains locked.
- uiDevice.sleep()
- lockStateTrackingRule.assertLocked()
- trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
- uiDevice.wakeUp()
- uiDevice.sleep()
- await()
- lockStateTrackingRule.assertUnlockedButNotReally()
- }
-
- @Test
fun grantDoesNotCallBack() {
val callback = mock<(GrantTrustResult) -> Unit>()
trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index 0121809..80d7947 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -64,13 +64,6 @@
wait("not trusted") { trustState.trusted == false }
}
- // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2
- fun assertUnlockedButNotReally() {
- wait("device unlocked") { !keyguardManager.isDeviceLocked }
- wait("not trusted") { trustState.trusted == false }
- wait("keyguard locked") { windowManager.isKeyguardLocked }
- }
-
fun assertUnlockedAndTrusted() {
wait("device unlocked") { !keyguardManager.isDeviceLocked }
wait("trusted") { trustState.trusted == true }