diff options
60 files changed, 2659 insertions, 488 deletions
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp index e092499cb41f..65bc8ccd12f8 100644 --- a/apct-tests/perftests/core/Android.bp +++ b/apct-tests/perftests/core/Android.bp @@ -44,6 +44,7 @@ android_test { "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 000000000000..bdc2a829a95b --- /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 000000000000..c69ae39846bd --- /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 000000000000..f8a3d5f2ed04 --- /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 000000000000..1a7258a802df --- /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 000000000000..dd9f4eb7e8d3 --- /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 000000000000..0655f45726ba --- /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 000000000000..7efabfd3758c --- /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 000000000000..4defe71fcddd --- /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 000000000000..3631c3f29287 --- /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 000000000000..ba2a65a17e84 --- /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 000000000000..78fe73262e4c --- /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/api/Android.bp b/api/Android.bp index fd2a6d29c377..bf4e6a11d25a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -157,6 +157,7 @@ genrule { genrule { name: "frameworks-base-api-system-current-compat", srcs: [ + ":android.api.public.latest", ":android.api.system.latest", ":android-incompatibilities.api.system.latest", ":frameworks-base-api-current.txt", @@ -165,33 +166,35 @@ genrule { out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + + "--check-compatibility:api:released $(location :android.api.public.latest) " + "--check-compatibility:api:released $(location :android.api.system.latest) " + - "--check-compatibility:base $(location :frameworks-base-api-current.txt) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + + "$(location :frameworks-base-api-current.txt) " + "$(location :frameworks-base-api-system-current.txt)", } genrule { name: "frameworks-base-api-module-lib-current-compat", srcs: [ + ":android.api.public.latest", + ":android.api.system.latest", ":android.api.module-lib.latest", ":android-incompatibilities.api.module-lib.latest", ":frameworks-base-api-current.txt", + ":frameworks-base-api-system-current.txt", ":frameworks-base-api-module-lib-current.txt", ], out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + + "--check-compatibility:api:released $(location :android.api.public.latest) " + + "--check-compatibility:api:released $(location :android.api.system.latest) " + "--check-compatibility:api:released $(location :android.api.module-lib.latest) " + - // Note: having "public" be the base of module-lib is not perfect -- it should - // ideally be a merged public+system (which metalava is not currently able to generate). - // This "base" will help when migrating from MODULE_LIBS -> public, but not when - // migrating from MODULE_LIBS -> system (where it needs to instead be listed as - // an incompatibility). - "--check-compatibility:base $(location :frameworks-base-api-current.txt) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + + "$(location :frameworks-base-api-current.txt) " + + "$(location :frameworks-base-api-system-current.txt) " + "$(location :frameworks-base-api-module-lib-current.txt)", } @@ -370,7 +373,6 @@ stubs_defaults { high_mem: true, // Lots of sources => high memory use, see b/170701554 installable: false, annotations_enabled: true, - previous_api: ":android.api.public.latest", merge_annotations_dirs: ["metalava-manual"], defaults_visibility: ["//frameworks/base/api"], visibility: [ diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 5b7e25bbbb4c..12820f9ff277 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -38,6 +38,9 @@ non_updatable_exportable_droidstubs { "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", ], + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.public.latest", check_api: { current: { api_file: ":non-updatable-current.txt", @@ -118,6 +121,9 @@ non_updatable_exportable_droidstubs { "module-classpath-stubs-defaults", ], flags: priv_apps, + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.system.latest", check_api: { current: { api_file: ":non-updatable-system-current.txt", @@ -178,6 +184,9 @@ non_updatable_exportable_droidstubs { "module-classpath-stubs-defaults", ], flags: test + priv_apps_in_stubs, + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.test.latest", check_api: { current: { api_file: ":non-updatable-test-current.txt", @@ -257,6 +266,9 @@ non_updatable_exportable_droidstubs { "module-classpath-stubs-defaults", ], flags: priv_apps_in_stubs + module_libs, + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.module-lib.latest", check_api: { current: { api_file: ":non-updatable-module-lib-current.txt", @@ -571,6 +583,9 @@ java_api_library { ], defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_stubs_current.from-text", + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.public.latest", } java_api_library { @@ -582,6 +597,9 @@ java_api_library { ], defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_system_stubs_current.from-text", + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.system.latest", } java_api_library { @@ -594,6 +612,9 @@ java_api_library { ], defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_test_stubs_current.from-text", + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.test.latest", } java_api_library { @@ -606,6 +627,9 @@ java_api_library { ], defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.module-lib.latest", } // This module generates a stub jar that is a union of the test and module lib @@ -623,6 +647,8 @@ java_api_library { defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", + // No need to specify previous_api as this is not used for compiling against. + // This module is only used for hiddenapi, and other modules should not // depend on this module. visibility: ["//visibility:private"], @@ -922,7 +948,7 @@ java_defaults { "i18n.module.public.api.stubs.source.system.api.contribution", "i18n.module.public.api.stubs.source.module_lib.api.contribution", ], - previous_api: ":android.api.public.latest", + previous_api: ":android.api.combined.module-lib.latest", } // Java API library definitions per API surface diff --git a/api/api.go b/api/api.go index 449fac63f90c..d4db49e90a01 100644 --- a/api/api.go +++ b/api/api.go @@ -478,7 +478,7 @@ func createApiContributionDefaults(ctx android.LoadHookContext, modules []string props.Api_contributions = transformArray( modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix)) props.Defaults_visibility = []string{"//visibility:public"} - props.Previous_api = proptools.StringPtr(":android.api.public.latest") + props.Previous_api = proptools.StringPtr(":android.api.combined." + sdkKind.String() + ".latest") ctx.CreateModule(java.DefaultsFactory, &props) } } diff --git a/core/api/current.txt b/core/api/current.txt index 2866e71dce91..d64593854749 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3454,6 +3454,8 @@ package android.accessibilityservice { 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 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index d70fa19a4468..81cc6742fb17 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -67,6 +67,7 @@ import android.view.accessibility.AccessibilityInteractionClient; 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 abstract class AccessibilityService extends Service { */ 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/content/Context.java b/core/java/android/content/Context.java index c7e5d88c299d..a6eed50a594a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4419,7 +4419,8 @@ public abstract class Context { * @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. @@ -4463,7 +4464,8 @@ public abstract class Context { * <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 e0cf0a5f8178..a475c2925881 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -932,7 +932,8 @@ public class ContextWrapper extends Context { } @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/content/pm/dex/OWNERS b/core/java/android/content/pm/dex/OWNERS index 267e5d58f9a6..558b5f795f77 100644 --- a/core/java/android/content/pm/dex/OWNERS +++ b/core/java/android/content/pm/dex/OWNERS @@ -1,7 +1,6 @@ # Bug component: 86431 -toddke@android.com -toddke@google.com patb@google.com -calin@google.com ngeoffray@google.com +jiakaiz@google.com +mast@google.com diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS index d2a2f12ec59f..51ad1519941b 100644 --- a/core/java/android/hardware/OWNERS +++ b/core/java/android/hardware/OWNERS @@ -5,7 +5,7 @@ michaelwr@google.com sumir@google.com # Camera -per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,yinchiayeh@google.com,zhijunhe@google.com,jchowdhary@google.com +per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com # Sensor Privacy per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS index feeef55a957b..92ea0cf601fc 100644 --- a/core/java/android/net/OWNERS +++ b/core/java/android/net/OWNERS @@ -3,6 +3,6 @@ set noparent include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS -per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com +per-file SSL*,Uri*,Url* = prb@google.com,sorinbasca@google.com,narayan@google.com,ngeoffray@google.com per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS per-file Uri.java,Uri.aidl = varunshah@google.com diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java index d008034f633b..cba4423831a2 100644 --- a/core/java/android/os/SharedMemory.java +++ b/core/java/android/os/SharedMemory.java @@ -25,8 +25,6 @@ import android.system.OsConstants; import dalvik.system.VMRuntime; -import libcore.io.IoUtils; - import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; @@ -65,7 +63,7 @@ public final class SharedMemory implements Parcelable, Closeable { mMemoryRegistration = new MemoryRegistration(mSize); mCleaner = Cleaner.create(mFileDescriptor, - new Closer(mFileDescriptor, mMemoryRegistration)); + new Closer(mFileDescriptor.getInt$(), mMemoryRegistration)); } /** @@ -328,20 +326,21 @@ public final class SharedMemory implements Parcelable, Closeable { * Cleaner that closes the FD */ private static final class Closer implements Runnable { - private FileDescriptor mFd; + private int mFd; private MemoryRegistration mMemoryReference; - private Closer(FileDescriptor fd, MemoryRegistration memoryReference) { + private Closer(int fd, MemoryRegistration memoryReference) { mFd = fd; - IoUtils.setFdOwner(mFd, this); mMemoryReference = memoryReference; } @Override public void run() { - IoUtils.closeQuietly(mFd); - mFd = null; - + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(mFd); + Os.close(fd); + } catch (ErrnoException e) { /* swallow error */ } mMemoryReference.release(); mMemoryReference = null; } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 685654a781dd..a62efdf37665 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -82,6 +82,20 @@ flag { 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" description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" bug: "302376158" diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS index fd73d35e00d2..0472b6c43ce6 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/jni/OWNERS b/core/jni/OWNERS index 1a27367a2b06..30ce63cfc744 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -2,7 +2,7 @@ per-file *Camera*,*camera* = file:platform/frameworks/av:/camera/OWNERS # Connectivity -per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com +per-file android_net_* = jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com # Choreographer per-file android_view_DisplayEventReceiver* = file:platform/frameworks/native:/services/surfaceflinger/OWNERS @@ -83,7 +83,7 @@ per-file android_text_* = file:/core/java/android/text/OWNERS # These are highly common-use files per-file Android.bp = file:/graphics/java/android/graphics/OWNERS per-file AndroidRuntime.cpp = file:/graphics/java/android/graphics/OWNERS -per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google.com +per-file AndroidRuntime.cpp = file:platform/art:main:/OWNERS # Although marked "view" this is mostly graphics stuff per-file android_view_* = file:/graphics/java/android/graphics/OWNERS # File used for Android Studio layoutlib diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 75cfba02120e..121f348bccd4 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -1037,7 +1037,7 @@ message AppsExitInfoProto { optional int32 uid = 1; repeated .android.app.ApplicationExitInfoProto app_exit_info = 2; - repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3; + repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3 [deprecated=true]; } repeated User users = 2; } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 59066eb83f1c..0b9bde835b99 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5937,6 +5937,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 54ab1470dea7..8b13a11eb46f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4433,6 +4433,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" /> diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index c8015d43b404..7b70b412e62b 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -87,7 +87,7 @@ public class ContentResolverTest { bitmap.compress(Bitmap.CompressFormat.PNG, 90, mImage.getOutputStream()); final AssetFileDescriptor afd = new AssetFileDescriptor( - ParcelFileDescriptor.dup(mImage.getFileDescriptor()), 0, mSize, null); + new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null); when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn( afd); } diff --git a/core/tests/coretests/src/android/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS index beb77dc8f4fd..831f444c6ed4 100644 --- a/core/tests/coretests/src/android/net/OWNERS +++ b/core/tests/coretests/src/android/net/OWNERS @@ -1,5 +1,5 @@ include /services/core/java/com/android/server/net/OWNERS -per-file SSL*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com +per-file SSL*,Url* = file:platform/libcore:main:/OWNERS_net_tests per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS per-file Uri* = varunshah@google.com diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 24aea371c094..ecf4eb4502df 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 @@ public class AndroidKeyStoreMaintenance { } /** - * 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/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS index 1385f42bc676..7ad68aac62c5 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 @@ madym@google.com nmusgrave@google.com pbdr@google.com tkachenkoi@google.com +vaniadesmonda@google.com diff --git a/libs/nativehelper_jvm/Android.bp b/libs/nativehelper_jvm/Android.bp new file mode 100644 index 000000000000..b5b70283551a --- /dev/null +++ b/libs/nativehelper_jvm/Android.bp @@ -0,0 +1,19 @@ +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +cc_library_host_static { + name: "libnativehelper_jvm", + srcs: [ + "JNIPlatformHelp.c", + "JniConstants.c", + "file_descriptor_jni.c", + ], + whole_static_libs: ["libnativehelper_any_vm"], + export_static_lib_headers: ["libnativehelper_any_vm"], + target: { + windows: { + enabled: true, + }, + }, +} diff --git a/libs/nativehelper_jvm/JNIPlatformHelp.c b/libs/nativehelper_jvm/JNIPlatformHelp.c new file mode 100644 index 000000000000..9df31a8caa7f --- /dev/null +++ b/libs/nativehelper_jvm/JNIPlatformHelp.c @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#include <nativehelper/JNIPlatformHelp.h> + +#include <stddef.h> + +#include "JniConstants.h" + +static int GetBufferPosition(JNIEnv* env, jobject nioBuffer) { + return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_position(env)); +} + +static int GetBufferLimit(JNIEnv* env, jobject nioBuffer) { + return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_limit(env)); +} + +static int GetBufferElementSizeShift(JNIEnv* env, jobject nioBuffer) { + jclass byteBufferClass = JniConstants_NioByteBufferClass(env); + jclass shortBufferClass = JniConstants_NioShortBufferClass(env); + jclass charBufferClass = JniConstants_NioCharBufferClass(env); + jclass intBufferClass = JniConstants_NioIntBufferClass(env); + jclass floatBufferClass = JniConstants_NioFloatBufferClass(env); + jclass longBufferClass = JniConstants_NioLongBufferClass(env); + jclass doubleBufferClass = JniConstants_NioDoubleBufferClass(env); + + // Check the type of the Buffer + if ((*env)->IsInstanceOf(env, nioBuffer, byteBufferClass)) { + return 0; + } else if ((*env)->IsInstanceOf(env, nioBuffer, shortBufferClass) || + (*env)->IsInstanceOf(env, nioBuffer, charBufferClass)) { + return 1; + } else if ((*env)->IsInstanceOf(env, nioBuffer, intBufferClass) || + (*env)->IsInstanceOf(env, nioBuffer, floatBufferClass)) { + return 2; + } else if ((*env)->IsInstanceOf(env, nioBuffer, longBufferClass) || + (*env)->IsInstanceOf(env, nioBuffer, doubleBufferClass)) { + return 3; + } + return 0; +} + +jarray jniGetNioBufferBaseArray(JNIEnv* env, jobject nioBuffer) { + jmethodID hasArrayMethod = JniConstants_NioBuffer_hasArray(env); + jboolean hasArray = (*env)->CallBooleanMethod(env, nioBuffer, hasArrayMethod); + if (hasArray) { + jmethodID arrayMethod = JniConstants_NioBuffer_array(env); + return (*env)->CallObjectMethod(env, nioBuffer, arrayMethod); + } else { + return NULL; + } +} + +int jniGetNioBufferBaseArrayOffset(JNIEnv* env, jobject nioBuffer) { + jmethodID hasArrayMethod = JniConstants_NioBuffer_hasArray(env); + jboolean hasArray = (*env)->CallBooleanMethod(env, nioBuffer, hasArrayMethod); + if (hasArray) { + jmethodID arrayOffsetMethod = JniConstants_NioBuffer_arrayOffset(env); + jint arrayOffset = (*env)->CallIntMethod(env, nioBuffer, arrayOffsetMethod); + const int position = GetBufferPosition(env, nioBuffer); + jint elementSizeShift = GetBufferElementSizeShift(env, nioBuffer); + return (arrayOffset + position) << elementSizeShift; + } else { + return 0; + } +} + +jlong jniGetNioBufferPointer(JNIEnv* env, jobject nioBuffer) { + // in Java 11, the address field of a HeapByteBuffer contains a non-zero value despite + // HeapByteBuffer being a non-direct buffer. In that case, this should still return 0. + jmethodID isDirectMethod = JniConstants_NioBuffer_isDirect(env); + jboolean isDirect = (*env)->CallBooleanMethod(env, nioBuffer, isDirectMethod); + if (isDirect == JNI_FALSE) { + return 0L; + } + jlong baseAddress = (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env)); + if (baseAddress != 0) { + const int position = GetBufferPosition(env, nioBuffer); + const int shift = GetBufferElementSizeShift(env, nioBuffer); + baseAddress += position << shift; + } + return baseAddress; +} + +jlong jniGetNioBufferFields(JNIEnv* env, jobject nioBuffer, + jint* position, jint* limit, jint* elementSizeShift) { + *position = GetBufferPosition(env, nioBuffer); + *limit = GetBufferLimit(env, nioBuffer); + *elementSizeShift = GetBufferElementSizeShift(env, nioBuffer); + return (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env)); +} diff --git a/libs/nativehelper_jvm/JniConstants.c b/libs/nativehelper_jvm/JniConstants.c new file mode 100644 index 000000000000..ca58f61070ba --- /dev/null +++ b/libs/nativehelper_jvm/JniConstants.c @@ -0,0 +1,199 @@ +/* + * 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. + */ + +#include "JniConstants.h" + +#include <pthread.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#define LOG_TAG "JniConstants" +#include <log/log.h> + +// jclass constants list: +// <class, signature, androidOnly> +#define JCLASS_CONSTANTS_LIST(V) \ + V(FileDescriptor, "java/io/FileDescriptor", false) \ + V(NioBuffer, "java/nio/Buffer", false) \ + V(NioByteBuffer, "java/nio/ByteBuffer", false) \ + V(NioShortBuffer, "java/nio/ShortBuffer", false) \ + V(NioCharBuffer, "java/nio/CharBuffer", false) \ + V(NioIntBuffer, "java/nio/IntBuffer", false) \ + V(NioFloatBuffer, "java/nio/FloatBuffer", false) \ + V(NioLongBuffer, "java/nio/LongBuffer", false) \ + V(NioDoubleBuffer, "java/nio/DoubleBuffer", false) + +// jmethodID's of public methods constants list: +// <Class, method, method-string, signature, is_static> +#define JMETHODID_CONSTANTS_LIST(V) \ + V(FileDescriptor, init, "<init>", "()V", false) \ + V(NioBuffer, array, "array", "()Ljava/lang/Object;", false) \ + V(NioBuffer, hasArray, "hasArray", "()Z", false) \ + V(NioBuffer, isDirect, "isDirect", "()Z", false) \ + V(NioBuffer, arrayOffset, "arrayOffset", "()I", false) + +// jfieldID constants list: +// <Class, field, signature, is_static> +#define JFIELDID_CONSTANTS_LIST(V) \ + V(FileDescriptor, fd, "I", false) \ + V(NioBuffer, address, "J", false) \ + V(NioBuffer, limit, "I", false) \ + V(NioBuffer, position, "I", false) + +#define CLASS_NAME(cls) g_ ## cls +#define METHOD_NAME(cls, method) g_ ## cls ## _ ## method +#define FIELD_NAME(cls, field) g_ ## cls ## _ ## field + +// +// Declare storage for cached classes, methods and fields. +// + +#define JCLASS_DECLARE_STORAGE(cls, ...) \ + static jclass CLASS_NAME(cls) = NULL; +JCLASS_CONSTANTS_LIST(JCLASS_DECLARE_STORAGE) +#undef JCLASS_DECLARE_STORAGE + +#define JMETHODID_DECLARE_STORAGE(cls, method, ...) \ + static jmethodID METHOD_NAME(cls, method) = NULL; +JMETHODID_CONSTANTS_LIST(JMETHODID_DECLARE_STORAGE) +#undef JMETHODID_DECLARE_STORAGE + +#define JFIELDID_DECLARE_STORAGE(cls, field, ...) \ + static jfieldID FIELD_NAME(cls, field) = NULL; +JFIELDID_CONSTANTS_LIST(JFIELDID_DECLARE_STORAGE) +#undef JFIELDID_DECLARE_STORAGE + +// +// Helper methods +// + +static jclass FindClass(JNIEnv* env, const char* signature, bool androidOnly) { + jclass cls = (*env)->FindClass(env, signature); + if (cls == NULL) { + LOG_ALWAYS_FATAL_IF(!androidOnly, "Class not found: %s", signature); + return NULL; + } + return (*env)->NewGlobalRef(env, cls); +} + +static jmethodID FindMethod(JNIEnv* env, jclass cls, + const char* name, const char* signature, bool isStatic) { + jmethodID method; + if (isStatic) { + method = (*env)->GetStaticMethodID(env, cls, name, signature); + } else { + method = (*env)->GetMethodID(env, cls, name, signature); + } + LOG_ALWAYS_FATAL_IF(method == NULL, "Method not found: %s:%s", name, signature); + return method; +} + +static jfieldID FindField(JNIEnv* env, jclass cls, + const char* name, const char* signature, bool isStatic) { + jfieldID field; + if (isStatic) { + field = (*env)->GetStaticFieldID(env, cls, name, signature); + } else { + field = (*env)->GetFieldID(env, cls, name, signature); + } + LOG_ALWAYS_FATAL_IF(field == NULL, "Field not found: %s:%s", name, signature); + return field; +} + +static pthread_once_t g_initialized = PTHREAD_ONCE_INIT; +static JNIEnv* g_init_env; + +static void InitializeConstants() { + // Initialize cached classes. +#define JCLASS_INITIALIZE(cls, signature, androidOnly) \ + CLASS_NAME(cls) = FindClass(g_init_env, signature, androidOnly); + JCLASS_CONSTANTS_LIST(JCLASS_INITIALIZE) +#undef JCLASS_INITIALIZE + + // Initialize cached methods. +#define JMETHODID_INITIALIZE(cls, method, name, signature, isStatic) \ + METHOD_NAME(cls, method) = \ + FindMethod(g_init_env, CLASS_NAME(cls), name, signature, isStatic); + JMETHODID_CONSTANTS_LIST(JMETHODID_INITIALIZE) +#undef JMETHODID_INITIALIZE + + // Initialize cached fields. +#define JFIELDID_INITIALIZE(cls, field, signature, isStatic) \ + FIELD_NAME(cls, field) = \ + FindField(g_init_env, CLASS_NAME(cls), #field, signature, isStatic); + JFIELDID_CONSTANTS_LIST(JFIELDID_INITIALIZE) +#undef JFIELDID_INITIALIZE +} + +void EnsureInitialized(JNIEnv* env) { + // This method has to be called in every cache accesses because library can be built + // 2 different ways and existing usage for compat version doesn't have a good hook for + // initialization and is widely used. + g_init_env = env; + pthread_once(&g_initialized, InitializeConstants); +} + +// API exported by libnativehelper_api.h. + +void jniUninitializeConstants() { + // Uninitialize cached classes, methods and fields. + // + // NB we assume the runtime is stopped at this point and do not delete global + // references. +#define JCLASS_INVALIDATE(cls, ...) CLASS_NAME(cls) = NULL; + JCLASS_CONSTANTS_LIST(JCLASS_INVALIDATE); +#undef JCLASS_INVALIDATE + +#define JMETHODID_INVALIDATE(cls, method, ...) METHOD_NAME(cls, method) = NULL; + JMETHODID_CONSTANTS_LIST(JMETHODID_INVALIDATE); +#undef JMETHODID_INVALIDATE + +#define JFIELDID_INVALIDATE(cls, field, ...) FIELD_NAME(cls, field) = NULL; + JFIELDID_CONSTANTS_LIST(JFIELDID_INVALIDATE); +#undef JFIELDID_INVALIDATE + + // If jniConstantsUninitialize is called, runtime has shutdown. Reset + // state as some tests re-start the runtime. + pthread_once_t o = PTHREAD_ONCE_INIT; + memcpy(&g_initialized, &o, sizeof(o)); +} + +// +// Accessors +// + +#define JCLASS_ACCESSOR_IMPL(cls, ...) \ +jclass JniConstants_ ## cls ## Class(JNIEnv* env) { \ + EnsureInitialized(env); \ + return CLASS_NAME(cls); \ +} +JCLASS_CONSTANTS_LIST(JCLASS_ACCESSOR_IMPL) +#undef JCLASS_ACCESSOR_IMPL + +#define JMETHODID_ACCESSOR_IMPL(cls, method, ...) \ +jmethodID JniConstants_ ## cls ## _ ## method(JNIEnv* env) { \ + EnsureInitialized(env); \ + return METHOD_NAME(cls, method); \ +} +JMETHODID_CONSTANTS_LIST(JMETHODID_ACCESSOR_IMPL) + +#define JFIELDID_ACCESSOR_IMPL(cls, field, ...) \ +jfieldID JniConstants_ ## cls ## _ ## field(JNIEnv* env) { \ + EnsureInitialized(env); \ + return FIELD_NAME(cls, field); \ +} +JFIELDID_CONSTANTS_LIST(JFIELDID_ACCESSOR_IMPL) diff --git a/libs/nativehelper_jvm/JniConstants.h b/libs/nativehelper_jvm/JniConstants.h new file mode 100644 index 000000000000..e7a266d72509 --- /dev/null +++ b/libs/nativehelper_jvm/JniConstants.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include <sys/cdefs.h> + +#include <jni.h> + +__BEGIN_DECLS + +// +// Classes in constants cache. +// +// NB The implementations of these methods are generated by the JCLASS_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jclass JniConstants_FileDescriptorClass(JNIEnv* env); +jclass JniConstants_NioByteBufferClass(JNIEnv* env); +jclass JniConstants_NioShortBufferClass(JNIEnv* env); +jclass JniConstants_NioCharBufferClass(JNIEnv* env); +jclass JniConstants_NioIntBufferClass(JNIEnv* env); +jclass JniConstants_NioFloatBufferClass(JNIEnv* env); +jclass JniConstants_NioLongBufferClass(JNIEnv* env); +jclass JniConstants_NioDoubleBufferClass(JNIEnv* env); + +// +// Methods in the constants cache. +// +// NB The implementations of these methods are generated by the JMETHODID_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jmethodID JniConstants_FileDescriptor_init(JNIEnv* env); +jmethodID JniConstants_NioBuffer_array(JNIEnv* env); +jmethodID JniConstants_NioBuffer_arrayOffset(JNIEnv* env); +jmethodID JniConstants_NioBuffer_hasArray(JNIEnv* env); +jmethodID JniConstants_NioBuffer_isDirect(JNIEnv* env); + +// +// Fields in the constants cache. +// +// NB The implementations of these methods are generated by the JFIELDID_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jfieldID JniConstants_FileDescriptor_fd(JNIEnv* env); +jfieldID JniConstants_NioBuffer_address(JNIEnv* env); +jfieldID JniConstants_NioBuffer_limit(JNIEnv* env); +jfieldID JniConstants_NioBuffer_position(JNIEnv* env); + +__END_DECLS diff --git a/libs/nativehelper_jvm/OWNERS b/libs/nativehelper_jvm/OWNERS new file mode 100644 index 000000000000..5d55f6e4319b --- /dev/null +++ b/libs/nativehelper_jvm/OWNERS @@ -0,0 +1,7 @@ +# Bug component: 326772 + +include /libs/hwui/OWNERS +include platform/libnativehelper:/OWNERS + +diegoperez@google.com +jgaillard@google.com diff --git a/libs/nativehelper_jvm/README b/libs/nativehelper_jvm/README new file mode 100644 index 000000000000..755c42261f43 --- /dev/null +++ b/libs/nativehelper_jvm/README @@ -0,0 +1,2 @@ +libnativehelper_jvm is a JVM-compatible version of libnativehelper. +It should be used instead of libnativehelper whenever a host library is meant to run on a JVM.
\ No newline at end of file diff --git a/libs/nativehelper_jvm/file_descriptor_jni.c b/libs/nativehelper_jvm/file_descriptor_jni.c new file mode 100644 index 000000000000..36880cd586ca --- /dev/null +++ b/libs/nativehelper_jvm/file_descriptor_jni.c @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#include <android/file_descriptor_jni.h> + +#include <stddef.h> + +#define LOG_TAG "file_descriptor_jni" +#include <log/log.h> + +#include "JniConstants.h" + +static void EnsureArgumentIsFileDescriptor(JNIEnv* env, jobject instance) { + LOG_ALWAYS_FATAL_IF(instance == NULL, "FileDescriptor is NULL"); + jclass jifd = JniConstants_FileDescriptorClass(env); + LOG_ALWAYS_FATAL_IF(!(*env)->IsInstanceOf(env, instance, jifd), + "Argument is not a FileDescriptor"); +} + +JNIEXPORT _Nullable jobject AFileDescriptor_create(JNIEnv* env) { + return (*env)->NewObject(env, + JniConstants_FileDescriptorClass(env), + JniConstants_FileDescriptor_init(env)); +} + +JNIEXPORT int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) { + EnsureArgumentIsFileDescriptor(env, fileDescriptor); + return (*env)->GetIntField(env, fileDescriptor, JniConstants_FileDescriptor_fd(env)); +} + +JNIEXPORT void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) { + EnsureArgumentIsFileDescriptor(env, fileDescriptor); + (*env)->SetIntField(env, fileDescriptor, JniConstants_FileDescriptor_fd(env), fd); +} diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml index 1dfdd29e480a..d0f797e5c6b8 100644 --- a/nfc/lint-baseline.xml +++ b/nfc/lint-baseline.xml @@ -210,4 +210,59 @@ column="23"/> </issue> + <issue + id="FlaggedApi" + message="Method `PollingFrame()` is a flagged API and should be inside an `if (Flags.nfcReadPollingLoop())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) to transfer requirement to caller`)" + errorLine1=" pollingFrames.add(new PollingFrame(frame));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/HostApduService.java" + line="335" + column="43"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `processPollingFrames()` is a flagged API and should be inside an `if (Flags.nfcReadPollingLoop())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) to transfer requirement to caller`)" + errorLine1=" processPollingFrames(pollingFrames);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/HostApduService.java" + line="337" + column="21"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)" + errorLine1=" mNfcOemExtension = new NfcOemExtension(mContext, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="895" + column="28"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVendorNciResponse()` is a flagged API and should be inside an `if (Flags.nfcVendorCmd())` check (or annotate the surrounding method `onVendorResponseReceived` with `@FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) to transfer requirement to caller`)" + errorLine1=" executor.execute(() -> callback.onVendorNciResponse(gid, oid, payload));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcVendorNciCallbackListener.java" + line="88" + column="44"/> + </issue> + + <issue + id="FlaggedApi" + message="Method `onVendorNciNotification()` is a flagged API and should be inside an `if (Flags.nfcVendorCmd())` check (or annotate the surrounding method `onVendorNotificationReceived` with `@FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD) to transfer requirement to caller`)" + errorLine1=" executor.execute(() -> callback.onVendorNciNotification(gid, oid, payload));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcVendorNciCallbackListener.java" + line="106" + column="44"/> + </issue> + </issues>
\ No newline at end of file diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index 5966c9f759fb..e4bc7b4660e1 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -11,3 +11,6 @@ ykhung@google.com # 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/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0c89a5dcbcf4..adcb8d4c0e5e 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -59,7 +59,7 @@ ] } ], - + "auto-end-to-end-postsubmit": [ { "name": "AndroidAutomotiveHomeTests", @@ -78,7 +78,7 @@ ] } ], - + "postsubmit": [ { // Permission indicators @@ -90,7 +90,7 @@ ] } ], - + // v2/sysui/suite/test-mapping-sysui-screenshot-test "sysui-screenshot-test": [ { @@ -128,7 +128,7 @@ ] } ], - + // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged "sysui-screenshot-test-staged": [ { @@ -153,5 +153,13 @@ } ] } + ], + "sysui-robo-test": [ + { + "name": "SystemUIGoogleRoboRNGTests" + }, + { + "name": "SystemUIGoogleRobo2RNGTests" + } ] } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index da49201db808..5c5ff1e793df 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.KeyCharacterMap; 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 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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; @@ -305,6 +318,14 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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); @@ -324,6 +345,8 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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(); } @@ -433,6 +456,14 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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; } @@ -569,6 +600,16 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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"; @@ -592,6 +633,9 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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) { @@ -612,7 +656,9 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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); @@ -645,6 +691,8 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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; } @@ -724,6 +772,18 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con 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/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java index b478d5cc9d50..da3e4871d21f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; @@ -25,11 +26,15 @@ import static org.mockito.Mockito.when; 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.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.KeyEvent; +import android.view.accessibility.Flags; import androidx.test.filters.SmallTest; @@ -43,6 +48,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; 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; @@ -76,6 +82,9 @@ public class SystemActionsTest extends SysuiTestCase { private SystemActions mSystemActions; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -130,4 +139,40 @@ public class SystemActionsTest extends SysuiTestCase { 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/services/OWNERS b/services/OWNERS index 3ce5ae170329..69e4e24f8fa3 100644 --- a/services/OWNERS +++ b/services/OWNERS @@ -1,7 +1,7 @@ per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION} # art-team@ manages the system server profile -per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com +per-file art-profile* = file:platform/art:main:/OWNERS_boot_profile per-file java/com/android/server/HsumBootUserInitializer.java = file:/MULTIUSER_OWNERS diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index 9747579fa231..2945af507d51 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.KeyCharacterMap; 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 @@ public class SystemActionPerformer { 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/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 666e5600a8b6..47b65eb885ab 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -88,6 +88,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.zip.GZIPOutputStream; @@ -104,9 +105,9 @@ public final class AppExitInfoTracker { private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); /** These are actions that the forEach* should take after each iteration */ - private static final int FOREACH_ACTION_NONE = 0; - private static final int FOREACH_ACTION_REMOVE_ITEM = 1; - private static final int FOREACH_ACTION_STOP_ITERATION = 2; + @VisibleForTesting static final int FOREACH_ACTION_NONE = 0; + @VisibleForTesting static final int FOREACH_ACTION_REMOVE_ITEM = 1; + @VisibleForTesting static final int FOREACH_ACTION_STOP_ITERATION = 2; private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8; @@ -125,7 +126,7 @@ public final class AppExitInfoTracker { private static final String APP_TRACE_FILE_SUFFIX = ".gz"; - private final Object mLock = new Object(); + @VisibleForTesting final Object mLock = new Object(); /** * Initialized in {@link #init} and read-only after that. @@ -410,6 +411,23 @@ public final class AppExitInfoTracker { } /** + * Certain types of crashes should not be updated. This could end up deleting valuable + * information, for example, if a test application crashes and then the `am instrument` + * finishes, then the crash whould be replaced with a `reason == USER_REQUESTED` + * ApplicationExitInfo from ActivityManager, and the original crash would be lost. + */ + private boolean preventExitInfoUpdate(final ApplicationExitInfo exitInfo) { + switch (exitInfo.getReason()) { + case ApplicationExitInfo.REASON_ANR: + case ApplicationExitInfo.REASON_CRASH: + case ApplicationExitInfo.REASON_CRASH_NATIVE: + return true; + default: + return false; + } + } + + /** * Make note when ActivityManagerService decides to kill an application process. */ @VisibleForTesting @@ -418,10 +436,10 @@ public final class AppExitInfoTracker { ApplicationExitInfo info = getExitInfoLocked( raw.getPackageName(), raw.getPackageUid(), raw.getPid()); - if (info == null) { + if (info == null || preventExitInfoUpdate(info)) { info = addExitInfoLocked(raw); } else { - // always override the existing info since we are now more informational. + // Override the existing info since we have more information. info.setReason(raw.getReason()); info.setSubReason(raw.getSubReason()); info.setStatus(0); @@ -431,24 +449,8 @@ public final class AppExitInfoTracker { scheduleLogToStatsdLocked(info, true); } - /** - * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't - * being killed but the crash should still be added to AppExitInfo. Also, because we're not - * crashing, don't log out to statsd. - */ - @VisibleForTesting - @GuardedBy("mLock") - void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) { - addExitInfoLocked(raw, /* recoverable */ true); - } - @GuardedBy("mLock") private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) { - return addExitInfoLocked(raw, /* recoverable */ false); - } - - @GuardedBy("mLock") - private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) { if (!mAppExitInfoLoaded.get()) { Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage"); return null; @@ -464,13 +466,13 @@ public final class AppExitInfoTracker { } } for (int i = 0; i < packages.length; i++) { - addExitInfoInnerLocked(packages[i], uid, info, recoverable); + addExitInfoInnerLocked(packages[i], uid, info); } // SDK sandbox exits are stored under both real and package UID if (Process.isSdkSandboxUid(uid)) { for (int i = 0; i < packages.length; i++) { - addExitInfoInnerLocked(packages[i], raw.getPackageUid(), info, recoverable); + addExitInfoInnerLocked(packages[i], raw.getPackageUid(), info); } } @@ -526,72 +528,77 @@ public final class AppExitInfoTracker { if (k != null) { uid = k; } - ArrayList<ApplicationExitInfo> tlist = mTmpInfoList; - tlist.clear(); final int targetUid = uid; + // Launder the modification bit through a `final` array, as Java doesn't allow you to mutate + // a captured boolean inside of a lambda. + final boolean[] isModified = {false}; forEachPackageLocked((packageName, records) -> { AppExitInfoContainer container = records.get(targetUid); if (container == null) { return FOREACH_ACTION_NONE; } - tlist.clear(); - container.getExitInfoLocked(pid, 1, tlist); - if (tlist.size() == 0) { - return FOREACH_ACTION_NONE; - } - ApplicationExitInfo info = tlist.get(0); - if (info.getRealUid() != targetUid) { - tlist.clear(); + mTmpInfoList.clear(); + container.getExitInfosLocked(pid, /* maxNum */ 0, mTmpInfoList); + if (mTmpInfoList.size() == 0) { return FOREACH_ACTION_NONE; } - // Okay found it, update its reason. - updateExistingExitInfoRecordLocked(info, status, reason); - return FOREACH_ACTION_STOP_ITERATION; + for (int i = 0, size = mTmpInfoList.size(); i < size; i++) { + ApplicationExitInfo info = mTmpInfoList.get(i); + if (info.getRealUid() != targetUid) { + continue; + } + // We only update the most recent `ApplicationExitInfo` for this pid, which will + // always be the first one we se as `getExitInfosLocked()` returns them sorted + // by most-recent-first. + isModified[0] = true; + updateExistingExitInfoRecordLocked(info, status, reason); + return FOREACH_ACTION_STOP_ITERATION; + } + return FOREACH_ACTION_NONE; }); - return tlist.size() > 0; + mTmpInfoList.clear(); + return isModified[0]; } /** * Get the exit info with matching package name, filterUid and filterPid (if > 0) */ @VisibleForTesting - void getExitInfo(final String packageName, final int filterUid, - final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results) { + void getExitInfo(final String packageName, final int filterUid, final int filterPid, + final int maxNum, final List<ApplicationExitInfo> results) { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - boolean emptyPackageName = TextUtils.isEmpty(packageName); - if (!emptyPackageName) { - // fast path + if (!TextUtils.isEmpty(packageName)) { + // Fast path - just a single package. AppExitInfoContainer container = mData.get(packageName, filterUid); if (container != null) { - container.getExitInfoLocked(filterPid, maxNum, results); + container.getExitInfosLocked(filterPid, maxNum, results); } - } else { - // slow path - final ArrayList<ApplicationExitInfo> list = mTmpInfoList2; - list.clear(); - // get all packages - forEachPackageLocked((name, records) -> { - AppExitInfoContainer container = records.get(filterUid); - if (container != null) { - mTmpInfoList.clear(); - list.addAll(container.toListLocked(mTmpInfoList, filterPid)); - } - return AppExitInfoTracker.FOREACH_ACTION_NONE; - }); - - Collections.sort(list, - (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); - int size = list.size(); - if (maxNum > 0) { - size = Math.min(size, maxNum); - } - for (int i = 0; i < size; i++) { - results.add(list.get(i)); + return; + } + + // Slow path - get all the packages. + forEachPackageLocked((name, records) -> { + AppExitInfoContainer container = records.get(filterUid); + if (container != null) { + container.getExitInfosLocked(filterPid, /* maxNum */ 0, results); } - list.clear(); + return AppExitInfoTracker.FOREACH_ACTION_NONE; + }); + + // And while the results for each package are sorted, we should + // sort over and trim the quantity of global results as well. + Collections.sort( + results, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); + if (maxNum <= 0) { + return; + } + + int elementsToRemove = results.size() - maxNum; + for (int i = 0; i < elementsToRemove; i++) { + results.removeLast(); } } } finally { @@ -606,12 +613,10 @@ public final class AppExitInfoTracker { @GuardedBy("mLock") private ApplicationExitInfo getExitInfoLocked(final String packageName, final int filterUid, final int filterPid) { - ArrayList<ApplicationExitInfo> list = mTmpInfoList; - list.clear(); - getExitInfo(packageName, filterUid, filterPid, 1, list); - - ApplicationExitInfo info = list.size() > 0 ? list.get(0) : null; - list.clear(); + mTmpInfoList.clear(); + getExitInfo(packageName, filterUid, filterPid, 1, mTmpInfoList); + ApplicationExitInfo info = mTmpInfoList.size() > 0 ? mTmpInfoList.getFirst() : null; + mTmpInfoList.clear(); return info; } @@ -878,8 +883,7 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info, - boolean recoverable) { + private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) { AppExitInfoContainer container = mData.get(packageName, uid); if (container == null) { container = new AppExitInfoContainer(mAppExitInfoHistoryListSize); @@ -893,11 +897,7 @@ public final class AppExitInfoTracker { } mData.put(packageName, uid, container); } - if (recoverable) { - container.addRecoverableCrashLocked(info); - } else { - container.addExitInfoLocked(info); - } + container.addExitInfoLocked(info); } @GuardedBy("mLock") @@ -1205,7 +1205,7 @@ public final class AppExitInfoTracker { forEachPackageLocked((name, records) -> { for (int i = records.size() - 1; i >= 0; i--) { final AppExitInfoContainer container = records.valueAt(i); - container.forEachRecordLocked((pid, info) -> { + container.forEachRecordLocked((info) -> { final File traceFile = info.getTraceFile(); if (traceFile != null) { allFiles.remove(traceFile.getName()); @@ -1322,90 +1322,72 @@ public final class AppExitInfoTracker { * A container class of {@link android.app.ApplicationExitInfo} */ final class AppExitInfoContainer { - private SparseArray<ApplicationExitInfo> mInfos; // index is a pid - private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid + private ArrayList<ApplicationExitInfo> mExitInfos; private int mMaxCapacity; private int mUid; // Application uid, not isolated uid. AppExitInfoContainer(final int maxCapacity) { - mInfos = new SparseArray<ApplicationExitInfo>(); - mRecoverableCrashes = new SparseArray<ApplicationExitInfo>(); + mExitInfos = new ArrayList<ApplicationExitInfo>(); mMaxCapacity = maxCapacity; } + @VisibleForTesting @GuardedBy("mLock") - void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid, - final int maxNum, ArrayList<ApplicationExitInfo> results) { - if (filterPid > 0) { - ApplicationExitInfo r = map.get(filterPid); - if (r != null) { - results.add(r); + void getExitInfosLocked( + final int filterPid, final int maxNum, List<ApplicationExitInfo> results) { + if (mExitInfos.size() == 0) { + return; + } + + // Most of the callers might only be interested with the most recent + // ApplicationExitInfo, and so we can special case an O(n) walk. + if (maxNum == 1) { + ApplicationExitInfo result = null; + for (int i = 0, size = mExitInfos.size(); i < size; i++) { + ApplicationExitInfo info = mExitInfos.get(i); + if (filterPid > 0 && info.getPid() != filterPid) { + continue; + } + + if (result == null || result.getTimestamp() < info.getTimestamp()) { + result = info; + } } + if (result != null) { + results.add(result); + } + return; + } + + mTmpInfoList2.clear(); + if (filterPid <= 0) { + mTmpInfoList2.addAll(mExitInfos); } else { - final int numRep = map.size(); - if (maxNum <= 0 || numRep <= maxNum) { - // Return all records. - for (int i = 0; i < numRep; i++) { - results.add(map.valueAt(i)); - } - Collections.sort(results, - (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); - } else { - if (maxNum == 1) { - // Most of the caller might be only interested with the most recent one - ApplicationExitInfo r = map.valueAt(0); - for (int i = 1; i < numRep; i++) { - ApplicationExitInfo t = map.valueAt(i); - if (r.getTimestamp() < t.getTimestamp()) { - r = t; - } - } - results.add(r); - } else { - // Huh, need to sort it out then. - ArrayList<ApplicationExitInfo> list = mTmpInfoList2; - list.clear(); - for (int i = 0; i < numRep; i++) { - list.add(map.valueAt(i)); - } - Collections.sort(list, - (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); - for (int i = 0; i < maxNum; i++) { - results.add(list.get(i)); - } - list.clear(); + for (int i = 0, size = mExitInfos.size(); i < size; i++) { + ApplicationExitInfo info = mExitInfos.get(i); + if (info.getPid() == filterPid) { + mTmpInfoList2.add(info); } } } - } - @GuardedBy("mLock") - void getExitInfoLocked(final int filterPid, final int maxNum, - ArrayList<ApplicationExitInfo> results) { - getInfosLocked(mInfos, filterPid, maxNum, results); + Collections.sort( + mTmpInfoList2, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); + if (maxNum <= 0) { + results.addAll(mTmpInfoList2); + return; + } + + int elementsToRemove = mTmpInfoList2.size() - maxNum; + for (int i = 0; i < elementsToRemove; i++) { + mTmpInfoList2.removeLast(); + } + results.addAll(mTmpInfoList2); } + @VisibleForTesting @GuardedBy("mLock") - void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) { - int size; - if ((size = map.size()) >= mMaxCapacity) { - int oldestIndex = -1; - long oldestTimeStamp = Long.MAX_VALUE; - for (int i = 0; i < size; i++) { - ApplicationExitInfo r = map.valueAt(i); - if (r.getTimestamp() < oldestTimeStamp) { - oldestTimeStamp = r.getTimestamp(); - oldestIndex = i; - } - } - if (oldestIndex >= 0) { - final File traceFile = map.valueAt(oldestIndex).getTraceFile(); - if (traceFile != null) { - traceFile.delete(); - } - map.removeAt(oldestIndex); - } - } + void addExitInfoLocked(ApplicationExitInfo info) { // Claim the state information if there is any int uid = info.getPackageUid(); // SDK sandbox app states and app traces are stored under real UID @@ -1420,24 +1402,39 @@ public final class AppExitInfoTracker { if (info.getTraceFile() == null) { info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid)); } - info.setAppTraceRetriever(mAppTraceRetriever); - map.append(pid, info); - } - @GuardedBy("mLock") - void addExitInfoLocked(ApplicationExitInfo info) { - addInfoLocked(mInfos, info); + mExitInfos.add(info); + if (mExitInfos.size() <= mMaxCapacity) { + return; + } + + ApplicationExitInfo oldest = null; + for (int i = 0, size = mExitInfos.size(); i < size; i++) { + ApplicationExitInfo info2 = mExitInfos.get(i); + if (oldest == null || info2.getTimestamp() < oldest.getTimestamp()) { + oldest = info2; + } + } + File traceFile = oldest.getTraceFile(); + if (traceFile != null) { + traceFile.delete(); + } + mExitInfos.remove(oldest); } @GuardedBy("mLock") - void addRecoverableCrashLocked(ApplicationExitInfo info) { - addInfoLocked(mRecoverableCrashes, info); + ApplicationExitInfo getLastExitInfoForPid(final int pid) { + mTmpInfoList.clear(); + getExitInfosLocked(pid, /* maxNum */ 1, mTmpInfoList); + ApplicationExitInfo info = mTmpInfoList.size() == 0 ? null : mTmpInfoList.getFirst(); + mTmpInfoList.clear(); + return info; } @GuardedBy("mLock") boolean appendTraceIfNecessaryLocked(final int pid, final File traceFile) { - final ApplicationExitInfo r = mInfos.get(pid); + final ApplicationExitInfo r = getLastExitInfoForPid(pid); if (r != null) { r.setTraceFile(traceFile); r.setAppTraceRetriever(mAppTraceRetriever); @@ -1447,49 +1444,36 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - void destroyLocked(SparseArray<ApplicationExitInfo> map) { - for (int i = map.size() - 1; i >= 0; i--) { - ApplicationExitInfo ai = map.valueAt(i); - final File traceFile = ai.getTraceFile(); + void destroyLocked() { + for (int i = 0, size = mExitInfos.size(); i < size; i++) { + ApplicationExitInfo info = mExitInfos.get(i); + final File traceFile = info.getTraceFile(); if (traceFile != null) { traceFile.delete(); } - ai.setTraceFile(null); - ai.setAppTraceRetriever(null); + info.setTraceFile(null); + info.setAppTraceRetriever(null); } } + /** + * Go through each record in an *unspecified* order, execute `callback()` on each element, + * and potentially do some action (stopping iteration, removing the element, etc.) based on + * the return value of the callback. + */ @GuardedBy("mLock") - void destroyLocked() { - destroyLocked(mInfos); - destroyLocked(mRecoverableCrashes); - } - - @GuardedBy("mLock") - void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) { + void forEachRecordLocked(final Function<ApplicationExitInfo, Integer> callback) { if (callback == null) return; - for (int i = mInfos.size() - 1; i >= 0; i--) { - switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { + for (int i = mExitInfos.size() - 1; i >= 0; i--) { + ApplicationExitInfo info = mExitInfos.get(i); + switch (callback.apply(info)) { case FOREACH_ACTION_STOP_ITERATION: return; case FOREACH_ACTION_REMOVE_ITEM: - final File traceFile = mInfos.valueAt(i).getTraceFile(); - if (traceFile != null) { + File traceFile; + if ((traceFile = info.getTraceFile()) != null) { traceFile.delete(); } - mInfos.removeAt(i); - break; - } - } - for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { - switch (callback.apply( - mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) { - case FOREACH_ACTION_STOP_ITERATION: return; - case FOREACH_ACTION_REMOVE_ITEM: - final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile(); - if (traceFile != null) { - traceFile.delete(); - } - mRecoverableCrashes.removeAt(i); + mExitInfos.remove(info); break; } } @@ -1497,30 +1481,20 @@ public final class AppExitInfoTracker { @GuardedBy("mLock") void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { - ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>(); - for (int i = mInfos.size() - 1; i >= 0; i--) { - list.add(mInfos.valueAt(i)); - } - for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { - list.add(mRecoverableCrashes.valueAt(i)); - } - Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); - int size = list.size(); - for (int i = 0; i < size; i++) { - list.get(i).dump(pw, prefix + " ", "#" + i, sdf); + mTmpInfoList.clear(); + getExitInfosLocked(/* filterPid */ 0, /* maxNum */ 0, mTmpInfoList); + for (int i = 0, size = mTmpInfoList.size(); i < size; i++) { + mTmpInfoList.get(i).dump(pw, prefix + " ", "#" + i, sdf); } + mTmpInfoList.clear(); } @GuardedBy("mLock") void writeToProto(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); proto.write(AppsExitInfoProto.Package.User.UID, mUid); - for (int i = 0; i < mInfos.size(); i++) { - mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); - } - for (int i = 0; i < mRecoverableCrashes.size(); i++) { - mRecoverableCrashes.valueAt(i).writeToProto( - proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); + for (int i = 0, size = mExitInfos.size(); i < size; i++) { + mExitInfos.get(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); } proto.end(token); } @@ -1539,14 +1513,7 @@ public final class AppExitInfoTracker { case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: { ApplicationExitInfo info = new ApplicationExitInfo(); info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); - mInfos.put(info.getPid(), info); - break; - } - case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: { - ApplicationExitInfo info = new ApplicationExitInfo(); - info.readFromProto( - proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); - mRecoverableCrashes.put(info.getPid(), info); + mExitInfos.add(info); break; } } @@ -1554,24 +1521,6 @@ public final class AppExitInfoTracker { proto.end(token); return mUid; } - - @GuardedBy("mLock") - List<ApplicationExitInfo> toListLocked(List<ApplicationExitInfo> list, int filterPid) { - if (list == null) { - list = new ArrayList<ApplicationExitInfo>(); - } - for (int i = mInfos.size() - 1; i >= 0; i--) { - if (filterPid == 0 || filterPid == mInfos.keyAt(i)) { - list.add(mInfos.valueAt(i)); - } - } - for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { - if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) { - list.add(mRecoverableCrashes.valueAt(i)); - } - } - return list; - } } /** @@ -1750,7 +1699,11 @@ public final class AppExitInfoTracker { case MSG_APP_RECOVERABLE_CRASH: { ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; synchronized (mLock) { - handleNoteAppRecoverableCrashLocked(raw); + // Unlike MSG_APP_KILL, this is a recoverable crash, and + // so we want to bypass the statsd app-kill logging. + // Hence, call `addExitInfoLocked()` directly instead of + // `handleNoteAppKillLocked()`. + addExitInfoLocked(raw); } recycleRawRecord(raw); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 98263df3c4e8..48072e84867f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -2296,7 +2296,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.println(); if (dumpConstants) { - mConstants.dump(ipw); + mFgConstants.dump(ipw); + mBgConstants.dump(ipw); } if (dumpHistory) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index ac29f85b495c..71576465e035 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -252,9 +252,6 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ -662,7 +659,6 @@ public class LockSettingsService extends ILockSettings.Stub { 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, @@ -899,13 +895,7 @@ public class LockSettingsService extends ILockSettings.Stub { 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())) { @@ -1089,32 +1079,14 @@ public class LockSettingsService extends ILockSettings.Stub { // 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); - } - } - 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); - } + if (!getBoolean(MIGRATED_SP_FULL, false, 0)) { + for (UserInfo user : mUserManager.getAliveUsers()) { + removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber); + synchronized (mSpManager) { + migrateUserToSpWithBoundKeysLocked(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; @@ -1122,30 +1094,6 @@ public class LockSettingsService extends ILockSettings.Stub { } @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); @@ -1454,11 +1402,6 @@ public class LockSettingsService extends ILockSettings.Stub { } @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 { @@ -2195,9 +2138,7 @@ public class LockSettingsService extends ILockSettings.Stub { return; } onSyntheticPasswordUnlocked(userId, result.syntheticPassword); - if (FIX_UNLOCKED_DEVICE_REQUIRED_KEYS) { - unlockKeystore(userId, result.syntheticPassword); - } + unlockKeystore(userId, result.syntheticPassword); unlockCeStorage(userId, result.syntheticPassword); } } @@ -2503,9 +2444,7 @@ public class LockSettingsService extends ILockSettings.Stub { // 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 @@ -2931,9 +2870,7 @@ public class LockSettingsService extends ILockSettings.Stub { 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; @@ -3048,9 +2985,6 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ -3061,11 +2995,7 @@ public class LockSettingsService extends ILockSettings.Stub { 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/timezonedetector/OWNERS b/services/core/java/com/android/server/timezonedetector/OWNERS index 485a0ddb06d8..4220d145754f 100644 --- a/services/core/java/com/android/server/timezonedetector/OWNERS +++ b/services/core/java/com/android/server/timezonedetector/OWNERS @@ -1,8 +1,7 @@ # Bug component: 847766 # This is the main list for platform time / time zone detection maintainers, for this dir and # ultimately referenced by other OWNERS files for components maintained by the same team. -nfuller@google.com +boullanger@google.com jmorace@google.com kanyinsola@google.com -mingaleev@google.com -narayan@google.com +mingaleev@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index f62c76e1505a..b3c31a9cfb64 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -267,6 +267,10 @@ public class TrustManagerService extends SystemService { return KeyStoreAuthorization.getInstance(); } + AlarmManager getAlarmManager() { + return mContext.getSystemService(AlarmManager.class); + } + Looper getLooper() { return Looper.myLooper(); } @@ -285,7 +289,7 @@ public class TrustManagerService extends SystemService { mLockPatternUtils = injector.getLockPatternUtils(); mKeyStoreAuthorization = injector.getKeyStoreAuthorization(); mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper()); - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mAlarmManager = injector.getAlarmManager(); } @Override @@ -845,12 +849,7 @@ public class TrustManagerService extends SystemService { 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; @@ -911,19 +910,15 @@ public class TrustManagerService extends SystemService { 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; - - mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId), - isWeakUnlockMethodEnabled(authUserId)); - } else { - mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(userId), false); - } + // 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 { // 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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index e7565ee0071d..45ea2db3fde8 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -629,9 +629,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { if (mLastWallpaper != null) { WallpaperData targetWallpaper = null; - if (mLastWallpaper.connection.containsDisplay(displayId)) { + if (mLastWallpaper.connection != null && + mLastWallpaper.connection.containsDisplay(displayId)) { targetWallpaper = mLastWallpaper; - } else if (mFallbackWallpaper.connection.containsDisplay(displayId)) { + } else if (mFallbackWallpaper != null && + mFallbackWallpaper.connection != null && + mFallbackWallpaper.connection.containsDisplay(displayId)) { targetWallpaper = mFallbackWallpaper; } if (targetWallpaper == null) return; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index e15942bb8f9a..adcbf5c9d059 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -84,7 +84,11 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Random; +import java.util.function.Function; import java.util.zip.GZIPInputStream; /** @@ -940,6 +944,228 @@ public class ApplicationExitInfoTest { } } + private ApplicationExitInfo createExitInfo(int i) { + ApplicationExitInfo info = new ApplicationExitInfo(); + info.setPid(i); + info.setTimestamp(1000 + i); + info.setPackageUid(2000); + return info; + } + + @SuppressWarnings("GuardedBy") + private ArrayList<ApplicationExitInfo> getExitInfosHelper( + AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum) { + ArrayList<ApplicationExitInfo> infos = new ArrayList<ApplicationExitInfo>(); + container.getExitInfosLocked(filterPid, maxNum, infos); + return infos; + } + + @SuppressWarnings("GuardedBy") + private void checkAreHelper(AppExitInfoTracker.AppExitInfoContainer container, int filterPid, + int maxNum, List<Integer> expected, Function<ApplicationExitInfo, Integer> func) { + ArrayList<Integer> values = new ArrayList<Integer>(); + getExitInfosHelper(container, filterPid, maxNum) + .forEach((exitInfo) -> values.add(func.apply(exitInfo))); + assertEquals(values, expected); + + HashMap<Integer, Integer> expectedMultiset = new HashMap<Integer, Integer>(); + expected.forEach( + (elem) -> expectedMultiset.put(elem, expectedMultiset.getOrDefault(elem, 0) + 1)); + // `maxNum` isn't a parameter supported by `forEachRecordLocked()s`, but we can emulate it + // by stopping iteration when we've seen enough elements. + int[] numElementsToObserveWrapped = {maxNum}; + container.forEachRecordLocked((exitInfo) -> { + // Same thing as above, `filterPid` isn't a parameter supported out of the box for + // `forEachRecordLocked()`, but we emulate it here. + if (filterPid > 0 && filterPid != exitInfo.getPid()) { + return AppExitInfoTracker.FOREACH_ACTION_NONE; + } + + Integer key = func.apply(exitInfo); + assertTrue(expectedMultiset.toString(), expectedMultiset.containsKey(key)); + Integer references = expectedMultiset.get(key); + if (references == 1) { + expectedMultiset.remove(key); + } else { + expectedMultiset.put(key, references - 1); + } + if (--numElementsToObserveWrapped[0] == 0) { + return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION; + } + return AppExitInfoTracker.FOREACH_ACTION_NONE; + }); + assertEquals(expectedMultiset.size(), 0); + } + + private void checkPidsAre(AppExitInfoTracker.AppExitInfoContainer container, int filterPid, + int maxNum, List<Integer> expectedPids) { + checkAreHelper(container, filterPid, maxNum, expectedPids, (exitInfo) -> exitInfo.getPid()); + } + + private void checkPidsAre( + AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedPids) { + checkPidsAre(container, 0, 0, expectedPids); + } + + private void checkTimestampsAre(AppExitInfoTracker.AppExitInfoContainer container, + int filterPid, int maxNum, List<Integer> expectedTimestamps) { + checkAreHelper(container, filterPid, maxNum, expectedTimestamps, + (exitInfo) -> (int) exitInfo.getTimestamp()); + } + + private void checkTimestampsAre( + AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedTimestamps) { + checkTimestampsAre(container, 0, 0, expectedTimestamps); + } + + @SuppressWarnings("GuardedBy") + private AppExitInfoTracker.AppExitInfoContainer createBasicContainer() { + AppExitInfoTracker.AppExitInfoContainer container = + mAppExitInfoTracker.new AppExitInfoContainer(3); + container.addExitInfoLocked(createExitInfo(10)); + container.addExitInfoLocked(createExitInfo(30)); + container.addExitInfoLocked(createExitInfo(20)); + return container; + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerGetExitInfosIsSortedNewestFirst() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + checkPidsAre(container, Arrays.asList(30, 20, 10)); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerRemovesOldestReports() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + container.addExitInfoLocked(createExitInfo(40)); + checkPidsAre(container, Arrays.asList(40, 30, 20)); + + container.addExitInfoLocked(createExitInfo(50)); + checkPidsAre(container, Arrays.asList(50, 40, 30)); + + container.addExitInfoLocked(createExitInfo(45)); + checkPidsAre(container, Arrays.asList(50, 45, 40)); + + // Adding an older report shouldn't remove the newer ones. + container.addExitInfoLocked(createExitInfo(15)); + checkPidsAre(container, Arrays.asList(50, 45, 40)); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerFilterByPid() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + assertEquals(1, getExitInfosHelper(container, 30, 0).size()); + assertEquals(30, getExitInfosHelper(container, 0, 0).get(0).getPid()); + + assertEquals(1, getExitInfosHelper(container, 30, 0).size()); + assertEquals(20, getExitInfosHelper(container, 20, 0).get(0).getPid()); + + assertEquals(1, getExitInfosHelper(container, 10, 0).size()); + assertEquals(10, getExitInfosHelper(container, 10, 0).get(0).getPid()); + + assertEquals(0, getExitInfosHelper(container, 1337, 0).size()); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerLimitQuantityOfResults() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1, Arrays.asList(30)); + checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1000, Arrays.asList(30)); + + checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1, Arrays.asList(20)); + checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1000, Arrays.asList(20)); + + checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1, Arrays.asList(10)); + checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1000, Arrays.asList(10)); + + checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1, Arrays.asList()); + checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1000, Arrays.asList()); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerLastExitInfoForPid() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + assertEquals(30, container.getLastExitInfoForPid(30).getPid()); + assertEquals(20, container.getLastExitInfoForPid(20).getPid()); + assertEquals(10, container.getLastExitInfoForPid(10).getPid()); + assertEquals(null, container.getLastExitInfoForPid(1337)); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerCanHoldMultipleFromSamePid() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + ApplicationExitInfo info = createExitInfo(100); + ApplicationExitInfo info2 = createExitInfo(100); + ApplicationExitInfo info3 = createExitInfo(100); + info2.setTimestamp(1337); + info3.setTimestamp(31337); + + container.addExitInfoLocked(info); + assertEquals(1100, container.getLastExitInfoForPid(100).getTimestamp()); + container.addExitInfoLocked(info2); + assertEquals(1337, container.getLastExitInfoForPid(100).getTimestamp()); + container.addExitInfoLocked(info3); + assertEquals(31337, container.getLastExitInfoForPid(100).getTimestamp()); + + checkPidsAre(container, Arrays.asList(100, 100, 100)); + checkTimestampsAre(container, Arrays.asList(31337, 1337, 1100)); + + checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(100, 100, 100)); + checkTimestampsAre( + container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(31337, 1337, 1100)); + + checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(100, 100)); + checkTimestampsAre( + container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(31337, 1337)); + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerIteration() throws Exception { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + checkPidsAre(container, Arrays.asList(30, 20, 10)); + + // Unfortunately relying on order for this test, which is implemented as "last inserted" -> + // "first inserted". Note that this is insertion order, not timestamp. Thus, it's 20 -> 30 + // -> 10, as defined by `createBasicContainer()`. + List<Integer> elements = Arrays.asList(20, 30, 10); + for (int i = 0, size = elements.size(); i < size; i++) { + ArrayList<Integer> processedEntries = new ArrayList<Integer>(); + final int finalIndex = i; + container.forEachRecordLocked((exitInfo) -> { + processedEntries.add(new Integer(exitInfo.getPid())); + if (exitInfo.getPid() == elements.get(finalIndex)) { + return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION; + } + return AppExitInfoTracker.FOREACH_ACTION_NONE; + }); + assertEquals(processedEntries, elements.subList(0, i + 1)); + } + } + + @Test + @SuppressWarnings("GuardedBy") + public void testContainerIterationRemove() throws Exception { + for (int pidToRemove : Arrays.asList(30, 20, 10)) { + AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer(); + container.forEachRecordLocked((exitInfo) -> { + if (exitInfo.getPid() == pidToRemove) { + return AppExitInfoTracker.FOREACH_ACTION_REMOVE_ITEM; + } + return AppExitInfoTracker.FOREACH_ACTION_NONE; + }); + ArrayList<Integer> pidsRemaining = new ArrayList<Integer>(Arrays.asList(30, 20, 10)); + pidsRemaining.remove(new Integer(pidToRemove)); + checkPidsAre(container, pidsRemaining); + } + } + private static int makeExitStatus(int exitCode) { return (exitCode << 8) & 0xff00; } 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 0532e04257d4..7aec42b7eceb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.trust; +import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; @@ -26,12 +28,22 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +import static java.util.Collections.singleton; + import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.AlarmManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustListener; @@ -60,20 +72,24 @@ import android.os.RemoteException; 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; +import android.service.trust.ITrustAgentService; +import android.service.trust.ITrustAgentServiceCallback; import android.service.trust.TrustAgentService; import android.testing.TestableContext; +import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.infra.AndroidFuture; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags; +import com.android.internal.widget.LockSettingsInternal; import com.android.modules.utils.testing.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -81,15 +97,19 @@ import com.android.server.SystemServiceManager; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class TrustManagerServiceTest { @@ -101,9 +121,6 @@ public class TrustManagerServiceTest { .build(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule public final MockContext mMockContext = new MockContext( ApplicationProvider.getApplicationContext()); @@ -115,21 +132,28 @@ public class TrustManagerServiceTest { private static final int PROFILE_USER_ID = 70; private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L }; private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L }; + private static final long RENEWABLE_TRUST_DURATION = 10000L; + private static final String GRANT_TRUST_MESSAGE = "granted"; + private static final String TAG = "TrustManagerServiceTest"; private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>(); private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>(); private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>(); + private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>(); private @Mock ActivityManager mActivityManager; + private @Mock AlarmManager mAlarmManager; private @Mock BiometricManager mBiometricManager; private @Mock DevicePolicyManager mDevicePolicyManager; private @Mock FaceManager mFaceManager; private @Mock FingerprintManager mFingerprintManager; private @Mock KeyStoreAuthorization mKeyStoreAuthorization; private @Mock LockPatternUtils mLockPatternUtils; + private @Mock LockSettingsInternal mLockSettingsInternal; private @Mock PackageManager mPackageManager; private @Mock UserManager mUserManager; private @Mock IWindowManager mWindowManager; + private @Mock ITrustListener mTrustListener; private HandlerThread mHandlerThread; private TrustManagerService mService; @@ -158,6 +182,9 @@ public class TrustManagerServiceTest { return null; }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID)); + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.addService(LockSettingsInternal.class, mLockSettingsInternal); + ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() { @Override public boolean matches(Intent argument) { @@ -176,6 +203,7 @@ public class TrustManagerServiceTest { when(mWindowManager.isKeyguardLocked()).thenReturn(true); mMockContext.addMockSystemService(ActivityManager.class, mActivityManager); + mMockContext.addMockSystemService(AlarmManager.class, mAlarmManager); mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager); mMockContext.addMockSystemService(FaceManager.class, mFaceManager); mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); @@ -197,6 +225,7 @@ public class TrustManagerServiceTest { verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE), binderArgumentCaptor.capture(), anyBoolean(), anyInt())); mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue()); + mTrustManager.registerTrustListener(mTrustListener); } private class MockInjector extends TrustManagerService.Injector { @@ -215,6 +244,11 @@ public class TrustManagerServiceTest { } @Override + AlarmManager getAlarmManager() { + return mAlarmManager; + } + + @Override Looper getLooper() { return mHandlerThread.getLooper(); } @@ -367,12 +401,10 @@ public class TrustManagerServiceTest { @Test public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException { - final ITrustListener trustListener = mock(ITrustListener.class); - mTrustManager.registerTrustListener(trustListener); mService.waitForIdle(); mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID); mService.waitForIdle(); - verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID); + verify(mTrustListener).onEnabledTrustAgentsChanged(TEST_USER_ID); } // Tests that when the device is locked for a managed profile with a *unified* challenge, the @@ -380,7 +412,6 @@ public class TrustManagerServiceTest { // 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); @@ -416,7 +447,169 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) + public void testSuccessfulUnlock_bindsTrustAgent() throws Exception { + when(mUserManager.getAliveUsers()) + .thenReturn(List.of(new UserInfo(TEST_USER_ID, "test", UserInfo.FLAG_FULL))); + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + assertThat(getCallback(trustAgentService)).isNotNull(); + } + + @Test + public void testSuccessfulUnlock_notifiesTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService).onUnlockAttempt(/* successful= */ true); + } + + @Test + public void testSuccessfulUnlock_notifiesTrustListenerOfChangeInManagedTrust() + throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + mService.waitForIdle(); + Mockito.reset(mTrustListener); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(mTrustListener).onTrustManagedChanged(false, TEST_USER_ID); + } + + @Test + @Ignore("TODO: b/340891566 - Trustagent always refreshes trustable timer for user 0 on unlock") + public void testSuccessfulUnlock_refreshesTrustableTimers() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgent = + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + setUpRenewableTrust(trustAgent); + + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + + // Idle and hard timeout alarms for first renewable trust granted + // Idle timeout alarm refresh for second renewable trust granted + // Idle and hard timeout alarms refresh for last report + verify(mAlarmManager, times(3)) + .setExact( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + anyLong(), + anyString(), + any(AlarmManager.OnAlarmListener.class), + any(Handler.class)); + } + + @Test + public void testFailedUnlock_doesNotBindTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired( + trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService, never()).setCallback(any()); + } + + @Test + public void testFailedUnlock_notifiesTrustAgent() throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + ITrustAgentService trustAgentService = + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(trustAgentService).onUnlockAttempt(/* successful= */ false); + } + + @Test + public void testFailedUnlock_doesNotNotifyTrustListenerOfChangeInManagedTrust() + throws Exception { + ComponentName trustAgentName = + ComponentName.unflattenFromString("com.android/.SystemTrustAgent"); + setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED); + Mockito.reset(mTrustListener); + + attemptFailedUnlock(TEST_USER_ID); + mService.waitForIdle(); + + verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt()); + } + + private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException { + ITrustAgentServiceCallback callback = getCallback(trustAgent); + callback.setManagingTrust(true); + mService.waitForIdle(); + attemptSuccessfulUnlock(TEST_USER_ID); + mService.waitForIdle(); + when(mWindowManager.isKeyguardLocked()).thenReturn(false); + grantRenewableTrust(callback); + } + + private ITrustAgentService setUpTrustAgentWithStrongAuthRequired( + ComponentName agentName, @StrongAuthFlags int strongAuthFlags) throws Exception { + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(TEST_USER_ID); + addTrustAgent(agentName, true); + mLockPatternUtils.setKnownTrustAgents(singleton(agentName), TEST_USER_ID); + mLockPatternUtils.setEnabledTrustAgents(singleton(agentName), TEST_USER_ID); + when(mUserManager.isUserUnlockingOrUnlocked(TEST_USER_ID)).thenReturn(true); + setupStrongAuthTracker(strongAuthFlags, false); + mService.waitForIdle(); + return getOrCreateMockTrustAgent(agentName); + } + + private void attemptSuccessfulUnlock(int userId) throws RemoteException { + mTrustManager.reportUnlockAttempt(/* successful= */ true, userId); + } + + private void attemptFailedUnlock(int userId) throws RemoteException { + mTrustManager.reportUnlockAttempt(/* successful= */ false, userId); + } + + private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException { + Log.i(TAG, "Granting trust"); + AndroidFuture<GrantTrustResult> future = new AndroidFuture<>(); + callback.grantTrust( + GRANT_TRUST_MESSAGE, + RENEWABLE_TRUST_DURATION, + FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE, + future); + mService.waitForIdle(); + } + + /** + * Retrieve the ITrustAgentServiceCallback attached to a TrustAgentService after it has been + * bound to by the TrustManagerService. Will fail if no binding was established. + */ + private ITrustAgentServiceCallback getCallback(ITrustAgentService trustAgentService) + throws RemoteException { + ArgumentCaptor<ITrustAgentServiceCallback> callbackCaptor = + ArgumentCaptor.forClass(ITrustAgentServiceCallback.class); + verify(trustAgentService).setCallback(callbackCaptor.capture()); + return callbackCaptor.getValue(); + } + + @Test public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -425,7 +618,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception { setupStrongAuthTrackerToAllowEverything(); setupFace(SensorProperties.STRENGTH_WEAK); @@ -433,7 +625,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -442,7 +633,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -451,7 +641,6 @@ public class TrustManagerServiceTest { } @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); @@ -459,7 +648,6 @@ public class TrustManagerServiceTest { } @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); @@ -468,7 +656,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -477,7 +664,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -486,7 +672,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy() throws Exception { @@ -498,7 +683,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy() throws Exception { setupStrongAuthTrackerToAllowEverything(); @@ -509,7 +693,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception { setupStrongAuthTrackerToAllowEverything(); setupFingerprint(SensorProperties.STRENGTH_STRONG); @@ -517,7 +700,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception { setupStrongAuthTrackerToAllowEverything(); setupFace(SensorProperties.STRENGTH_STRONG); @@ -525,7 +707,6 @@ public class TrustManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2) public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception { setupStrongAuthTrackerToAllowEverything(); verifyWeakUnlockDisabled(); @@ -637,6 +818,20 @@ public class TrustManagerServiceTest { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.serviceInfo = serviceInfo; mTrustAgentResolveInfoList.add(resolveInfo); + ITrustAgentService.Stub mockService = getOrCreateMockTrustAgent(agentComponentName); + mMockContext.addMockService(agentComponentName, mockService); + mMockTrustAgents.put(agentComponentName, mockService); + } + + private ITrustAgentService.Stub getOrCreateMockTrustAgent(ComponentName agentComponentName) { + return mMockTrustAgents.computeIfAbsent( + agentComponentName, + (componentName) -> { + ITrustAgentService.Stub mockTrustAgent = mock(ITrustAgentService.Stub.class); + when(mockTrustAgent.queryLocalInterface(anyString())) + .thenReturn(mockTrustAgent); + return mockTrustAgent; + }); } private void bootService() { 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 f9077c4ae602..93fc071a5bb7 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 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - void setKeystorePassword(byte[] password, int userHandle) { - - } - - @Override void initKeystoreSuperKeys(int userId, SyntheticPassword sp, boolean allowExisting) { } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index a8a90170ae4d..ba33eab5331a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -33,6 +33,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; @@ -136,6 +137,7 @@ public class UsageStatsDatabase { // The obfuscated packages to tokens mappings file private final File mPackageMappingsFile; // Holds all of the data related to the obfuscated packages and their token mappings. + @GuardedBy("mLock") final PackagesTokenData mPackagesTokenData = new PackagesTokenData(); /** @@ -771,27 +773,30 @@ public class UsageStatsDatabase { * all of the stats at once has an amortized cost for future calls. */ void filterStats(IntervalStats stats) { - if (mPackagesTokenData.removedPackagesMap.isEmpty()) { - return; - } - final ArrayMap<String, Long> removedPackagesMap = mPackagesTokenData.removedPackagesMap; - - // filter out package usage stats - final int removedPackagesSize = removedPackagesMap.size(); - for (int i = 0; i < removedPackagesSize; i++) { - final String removedPackage = removedPackagesMap.keyAt(i); - final UsageStats usageStats = stats.packageStats.get(removedPackage); - if (usageStats != null && usageStats.mEndTimeStamp < removedPackagesMap.valueAt(i)) { - stats.packageStats.remove(removedPackage); + synchronized (mLock) { + if (mPackagesTokenData.removedPackagesMap.isEmpty()) { + return; + } + final ArrayMap<String, Long> removedPackagesMap = mPackagesTokenData.removedPackagesMap; + + // filter out package usage stats + final int removedPackagesSize = removedPackagesMap.size(); + for (int i = 0; i < removedPackagesSize; i++) { + final String removedPackage = removedPackagesMap.keyAt(i); + final UsageStats usageStats = stats.packageStats.get(removedPackage); + if (usageStats != null && + usageStats.mEndTimeStamp < removedPackagesMap.valueAt(i)) { + stats.packageStats.remove(removedPackage); + } } - } - // filter out events - for (int i = stats.events.size() - 1; i >= 0; i--) { - final UsageEvents.Event event = stats.events.get(i); - final Long timeRemoved = removedPackagesMap.get(event.mPackage); - if (timeRemoved != null && timeRemoved > event.mTimeStamp) { - stats.events.remove(i); + // filter out events + for (int i = stats.events.size() - 1; i >= 0; i--) { + final UsageEvents.Event event = stats.events.get(i); + final Long timeRemoved = removedPackagesMap.get(event.mPackage); + if (timeRemoved != null && timeRemoved > event.mTimeStamp) { + stats.events.remove(i); + } } } } @@ -1226,12 +1231,14 @@ public class UsageStatsDatabase { } void obfuscateCurrentStats(IntervalStats[] currentStats) { - if (mCurrentVersion < 5) { - return; - } - for (int i = 0; i < currentStats.length; i++) { - final IntervalStats stats = currentStats[i]; - stats.obfuscateData(mPackagesTokenData); + synchronized (mLock) { + if (mCurrentVersion < 5) { + return; + } + for (int i = 0; i < currentStats.length; i++) { + final IntervalStats stats = currentStats[i]; + stats.obfuscateData(mPackagesTokenData); + } } } diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS index 57303e748738..64775f824fa4 100644 --- a/tests/BootImageProfileTest/OWNERS +++ b/tests/BootImageProfileTest/OWNERS @@ -1,4 +1 @@ -calin@google.com -ngeoffray@google.com -vmarko@google.com -yawanng@google.com +include platform/art:main:/OWNERS_boot_profile diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt index d0e56268a27d..0c3c7e2af6f2 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 @@ class GrantAndRevokeTrustTest { .around(ScreenLockRule()) .around(lockStateTrackingRule) .around(trustAgentRule) - .around(DeviceFlagsValueProvider.createCheckFlagsRule()) @Before fun manageTrust() { @@ -93,7 +89,6 @@ class GrantAndRevokeTrustTest { } @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 @@ class GrantAndRevokeTrustTest { } @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 01218099f34c..80d79478c898 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 @@ class LockStateTrackingRule : TestRule { 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 } |