Merge "Add comments to AppWidgetServiceImpl [Part 2]" into main
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
index c69ae39..36266de 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/CipherEncryptPerfTest.java
@@ -23,6 +23,9 @@
import org.conscrypt.TestUtils;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
@@ -91,21 +94,17 @@
}
}
- 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)},
- };
+ public Collection <Object[]> getParams() {
+ final List<Object[]> params = new ArrayList<>();
+ for (BufferType bufferType : BufferType.values()) {
+ for (CipherFactory cipherFactory : MyCipherFactory.values()) {
+ for (Transformation transformation : Transformation.values()) {
+ params.add(new Object[] {new Config(
+ bufferType, cipherFactory, transformation)});
+ }
+ }
+ }
+ return params;
}
private EncryptStrategy encryptStrategy;
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index dd9f4eb..2643bae 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -30,6 +30,9 @@
import java.io.OutputStream;
import java.net.SocketException;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -104,19 +107,26 @@
}
}
- private Object[] getParams() {
- return new Object[][] {
- new Object[] {new Config(
- EndpointFactory.CONSCRYPT,
- EndpointFactory.CONSCRYPT,
- 64,
- "AES128-GCM",
- ChannelType.CHANNEL,
- PerfTestProtocol.TLSv13)},
- };
+ public Collection getParams() {
+ final List<Object[]> params = new ArrayList<>();
+ for (EndpointFactory endpointFactory : EndpointFactory.values()) {
+ for (ChannelType channelType : ChannelType.values()) {
+ for (PerfTestProtocol protocol : PerfTestProtocol.values()) {
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 64, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType, protocol)});
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 512, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType, protocol)});
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 4096, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType, protocol)});
+ }
+ }
+ }
+ return params;
}
-
private ClientEndpoint client;
private ServerEndpoint server;
private byte[] message;
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java
new file mode 100644
index 0000000..8a0d52d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineFactory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.TestUtils;
+import java.security.Security;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+
+/**
+ * Factory for {@link SSLEngine} instances.
+ */
+public class EngineFactory {
+ public EngineFactory() {
+ this(newConscryptClientContext(), newConscryptServerContext());
+ }
+
+ private EngineFactory(SSLContext clientContext, SSLContext serverContext) {
+ this.clientContext = clientContext;
+ this.serverContext = serverContext;
+ }
+
+ private final SSLContext clientContext;
+ private final SSLContext serverContext;
+
+ public SSLEngine newClientEngine(String cipher) {
+ SSLEngine engine = initEngine(clientContext.createSSLEngine(), cipher, true);
+ return engine;
+ }
+
+ public SSLEngine newServerEngine(String cipher) {
+ SSLEngine engine = initEngine(serverContext.createSSLEngine(), cipher, false);
+ return engine;
+ }
+
+ public void dispose(SSLEngine engine) {
+ engine.closeOutbound();
+ }
+
+ private static SSLContext newConscryptClientContext() {
+ return TestUtils.newClientSslContext(TestUtils.getConscryptProvider());
+ }
+
+ private static SSLContext newConscryptServerContext() {
+ return TestUtils.newServerSslContext(TestUtils.getConscryptProvider());
+ }
+
+ static SSLEngine initEngine(SSLEngine engine, String cipher, boolean client) {
+ engine.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
+ engine.setEnabledCipherSuites(new String[] {cipher});
+ engine.setUseClientMode(client);
+ return engine;
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java
new file mode 100644
index 0000000..cd0ac96
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineHandshakePerfTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Benchmark comparing handshake performance of various engine implementations to conscrypt.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class EngineHandshakePerfTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Provider for the test configuration
+ */
+ private class Config {
+ BufferType a_bufferType;
+ String c_cipher;
+ int d_rttMillis;
+ Config(BufferType bufferType,
+ String cipher,
+ int rttMillis) {
+ a_bufferType = bufferType;
+ c_cipher = cipher;
+ d_rttMillis = rttMillis;
+ }
+ public BufferType bufferType() {
+ return a_bufferType;
+ }
+
+ public String cipher() {
+ return c_cipher;
+ }
+
+ public int rttMillis() {
+ return d_rttMillis;
+ }
+ }
+
+ public Collection getParams() {
+ final List<Object[]> params = new ArrayList<>();
+ for (BufferType bufferType : BufferType.values()) {
+ params.add(new Object[] {new Config(bufferType,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 100)});
+ }
+ return params;
+ }
+
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
+
+ private EngineFactory engineFactory = new EngineFactory();
+ private String cipher;
+ private int rttMillis;
+
+ private ByteBuffer clientApplicationBuffer;
+ private ByteBuffer clientPacketBuffer;
+ private ByteBuffer serverApplicationBuffer;
+ private ByteBuffer serverPacketBuffer;
+
+ private void setup(Config config) throws Exception {
+ cipher = config.cipher();
+ rttMillis = config.rttMillis();
+ BufferType bufferType = config.bufferType();
+
+ SSLEngine clientEngine = engineFactory.newClientEngine(cipher);
+ SSLEngine serverEngine = engineFactory.newServerEngine(cipher);
+
+ // Create the application and packet buffers for both endpoints.
+ clientApplicationBuffer = bufferType.newApplicationBuffer(clientEngine);
+ serverApplicationBuffer = bufferType.newApplicationBuffer(serverEngine);
+ clientPacketBuffer = bufferType.newPacketBuffer(clientEngine);
+ serverPacketBuffer = bufferType.newPacketBuffer(serverEngine);
+
+ engineFactory.dispose(clientEngine);
+ engineFactory.dispose(serverEngine);
+ }
+
+ @Test
+ @Parameters(method = "getParams")
+ public void handshake(Config config) throws Exception {
+ setup(config);
+ SSLEngine client = engineFactory.newClientEngine(cipher);
+ SSLEngine server = engineFactory.newServerEngine(cipher);
+ clientApplicationBuffer.clear();
+ clientPacketBuffer.clear();
+ serverApplicationBuffer.clear();
+ serverPacketBuffer.clear();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ client.beginHandshake();
+ server.beginHandshake();
+ doHandshake(client, server);
+ }
+
+ engineFactory.dispose(client);
+ engineFactory.dispose(server);
+ }
+
+ private void doHandshake(SSLEngine client, SSLEngine server) throws SSLException {
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ // Send as many client-to-server messages as possible
+ doHalfHandshake(client, server, clientPacketBuffer, serverApplicationBuffer);
+
+ if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
+ && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+ return;
+ }
+
+ // Do the same with server-to-client messages
+ doHalfHandshake(server, client, serverPacketBuffer, clientApplicationBuffer);
+
+ if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
+ && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
+ return;
+ }
+ }
+ }
+
+ private void doHalfHandshake(SSLEngine sender, SSLEngine receiver,
+ ByteBuffer senderPacketBuffer, ByteBuffer receiverApplicationBuffer)
+ throws SSLException {
+ SSLEngineResult senderResult;
+ SSLEngineResult receiverResult;
+
+ do {
+ senderResult = sender.wrap(EMPTY_BUFFER, senderPacketBuffer);
+ runDelegatedTasks(senderResult, sender);
+ senderPacketBuffer.flip();
+ receiverResult = receiver.unwrap(senderPacketBuffer, receiverApplicationBuffer);
+ runDelegatedTasks(receiverResult, receiver);
+ senderPacketBuffer.compact();
+ } while (senderResult.getHandshakeStatus() == HandshakeStatus.NEED_WRAP);
+
+ if (rttMillis > 0) {
+ try {
+ Thread.sleep(rttMillis / 2);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) {
+ if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
+ for (;;) {
+ Runnable task = engine.getDelegatedTask();
+ if (task == null) {
+ break;
+ }
+ task.run();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java
new file mode 100644
index 0000000..1fee218
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/EngineWrapPerfTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you 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 static org.conscrypt.TestUtils.doEngineHandshake;
+import static org.conscrypt.TestUtils.newTextMessage;
+import static org.junit.Assert.assertEquals;
+
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+
+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 comparing performance of various engine implementations to conscrypt.
+ */
+@RunWith(JUnitParamsRunner.class)
+@LargeTest
+public final class EngineWrapPerfTest {
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Provider for the benchmark configuration
+ */
+ private class Config {
+ BufferType a_bufferType;
+ int c_messageSize;
+ String d_cipher;
+ Config(BufferType bufferType,
+ int messageSize,
+ String cipher) {
+ a_bufferType = bufferType;
+ c_messageSize = messageSize;
+ d_cipher = cipher;
+ }
+ public BufferType bufferType() {
+ return a_bufferType;
+ }
+
+ public int messageSize() {
+ return c_messageSize;
+ }
+
+ public String cipher() {
+ return d_cipher;
+ }
+ }
+
+ public Collection getParams() {
+ final List<Object[]> params = new ArrayList<>();
+ for (BufferType bufferType : BufferType.values()) {
+ params.add(new Object[] {new Config(bufferType, 64,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+ params.add(new Object[] {new Config(bufferType, 512,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+ params.add(new Object[] {new Config(bufferType, 4096,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")});
+ }
+ return params;
+ }
+
+
+ private EngineFactory engineFactory = new EngineFactory();
+ private String cipher;
+ private SSLEngine clientEngine;
+ private SSLEngine serverEngine;
+
+ private ByteBuffer messageBuffer;
+ private ByteBuffer clientApplicationBuffer;
+ private ByteBuffer clientPacketBuffer;
+ private ByteBuffer serverApplicationBuffer;
+ private ByteBuffer serverPacketBuffer;
+ private ByteBuffer preEncryptedBuffer;
+
+ private void setup(Config config) throws Exception {
+ cipher = config.cipher();
+ BufferType bufferType = config.bufferType();
+
+ clientEngine = engineFactory.newClientEngine(cipher);
+ serverEngine = engineFactory.newServerEngine(cipher);
+
+ // Create the application and packet buffers for both endpoints.
+ clientApplicationBuffer = bufferType.newApplicationBuffer(clientEngine);
+ serverApplicationBuffer = bufferType.newApplicationBuffer(serverEngine);
+ clientPacketBuffer = bufferType.newPacketBuffer(clientEngine);
+ serverPacketBuffer = bufferType.newPacketBuffer(serverEngine);
+
+ // Generate the message to be sent from the client.
+ int messageSize = config.messageSize();
+ messageBuffer = bufferType.newBuffer(messageSize);
+ messageBuffer.put(newTextMessage(messageSize));
+ messageBuffer.flip();
+
+ // Complete the initial TLS handshake.
+ doEngineHandshake(clientEngine, serverEngine, clientApplicationBuffer, clientPacketBuffer,
+ serverApplicationBuffer, serverPacketBuffer, true);
+
+ // Populate the pre-encrypted buffer for use with the unwrap benchmark.
+ preEncryptedBuffer = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize());
+ doWrap(messageBuffer, preEncryptedBuffer);
+ doUnwrap(preEncryptedBuffer, serverApplicationBuffer);
+ }
+
+ void teardown() {
+ engineFactory.dispose(clientEngine);
+ engineFactory.dispose(serverEngine);
+ }
+
+ @Test
+ @Parameters(method = "getParams")
+ public void wrap(Config config) throws Exception {
+ setup(config);
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ // Reset the buffers.
+ messageBuffer.position(0);
+ clientPacketBuffer.clear();
+ // Wrap the original message and create the encrypted data.
+ doWrap(messageBuffer, clientPacketBuffer);
+
+ // Lightweight comparison - just make sure the data length is correct.
+ assertEquals(preEncryptedBuffer.limit(), clientPacketBuffer.limit());
+ }
+ teardown();
+ }
+
+ /**
+ * Simple benchmark that sends a single message from client to server.
+ */
+ @Test
+ @Parameters(method = "getParams")
+ public void wrapAndUnwrap(Config config) throws Exception {
+ setup(config);
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ // Reset the buffers.
+ messageBuffer.position(0);
+ clientPacketBuffer.clear();
+ serverApplicationBuffer.clear();
+ // Wrap the original message and create the encrypted data.
+ doWrap(messageBuffer, clientPacketBuffer);
+
+ // Unwrap the encrypted data and get back the original result.
+ doUnwrap(clientPacketBuffer, serverApplicationBuffer);
+
+ // Lightweight comparison - just make sure the unencrypted data length is correct.
+ assertEquals(messageBuffer.limit(), serverApplicationBuffer.limit());
+ }
+ teardown();
+ }
+
+ private void doWrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+ // Wrap the original message and create the encrypted data.
+ verifyResult(src, clientEngine.wrap(src, dst));
+ dst.flip();
+ }
+
+ private void doUnwrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+ verifyResult(src, serverEngine.unwrap(src, dst));
+ dst.flip();
+ }
+
+ private void verifyResult(ByteBuffer src, SSLEngineResult result) {
+ if (result.getStatus() != SSLEngineResult.Status.OK) {
+ throw new RuntimeException("Operation returned unexpected result " + result);
+ }
+ if (result.bytesConsumed() != src.limit()) {
+ throw new RuntimeException(
+ String.format(Locale.US,
+ "Operation didn't consume all bytes. Expected %d, consumed %d.",
+ src.limit(), result.bytesConsumed()));
+ }
+ }
+}
\ 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
index ba2a65a..4f285ff 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -24,6 +24,9 @@
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -94,15 +97,22 @@
}
}
- private Object[] getParams() {
- return new Object[][] {
- new Object[] {new Config(
- EndpointFactory.CONSCRYPT,
- EndpointFactory.CONSCRYPT,
- 64,
- "AES128-GCM",
- ChannelType.CHANNEL)},
- };
+ public Collection getParams() {
+ final List<Object[]> params = new ArrayList<>();
+ for (EndpointFactory endpointFactory : EndpointFactory.values()) {
+ for (ChannelType channelType : ChannelType.values()) {
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 64,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 512,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+ params.add(new Object[] {new Config(endpointFactory,
+ endpointFactory, 4096,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", channelType)});
+ }
+ }
+ return params;
}
private ClientEndpoint client;
@@ -121,7 +131,8 @@
final ChannelType channelType = config.channelType();
server = config.serverFactory().newServer(
- channelType, config.messageSize(), getCommonProtocolSuites(), ciphers(config));
+ channelType, config.messageSize(),
+ new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
server.setMessageProcessor(new MessageProcessor() {
@Override
public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
@@ -145,7 +156,8 @@
// Always use the same client for consistency across the benchmarks.
client = config.clientFactory().newClient(
- ChannelType.CHANNEL, server.port(), getCommonProtocolSuites(), ciphers(config));
+ ChannelType.CHANNEL, server.port(),
+ new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
client.start();
// Wait for the initial connection to complete.
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
index 78fe732..3542b0a 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/Transformation.java
@@ -31,7 +31,6 @@
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) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index da7997d..fe14d457 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -974,6 +974,11 @@
: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The hide request first finishes the animation and then proceeds to the server
+ // side, finally reaching here, marking this the end state.
+ ImeTracker.forLogging().onHidden(statsToken);
+ }
}
/**
@@ -3104,6 +3109,13 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_SHOW_WINDOW);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The ImeInsetsSourceProvider need the statsToken when dispatching the control
+ // (whenever the IME has drawn and its window is visible). Therefore, sending the
+ // statsToken here first.
+ notifyPreImeWindowVisibilityChanged(true /* visible */, statsToken);
+ }
+
ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", mDumper,
null /* icProto */);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow");
@@ -3127,7 +3139,9 @@
if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
mWindow.show();
mDecorViewWasVisible = true;
- applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken);
+ }
cancelImeSurfaceRemoval();
mInShowWindow = false;
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3238,6 +3252,20 @@
? mCurShowInputToken : mCurHideInputToken, setVisible, statsToken);
}
+ /**
+ * Notifies the ImeInsetsSourceProvider before the IME visibility changes.
+ *
+ * @param visible {@code true} if it became visible, {@code false} otherwise.
+ * @param statsToken the token tracking the current IME request.
+ */
+ private void notifyPreImeWindowVisibilityChanged(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
+ final var viewRootImpl = getWindow().getWindow().getDecorView().getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.notifyImeVisibilityChanged(visible, statsToken);
+ }
+ }
+
private void finishViews(boolean finishingInput) {
if (mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
@@ -3279,7 +3307,13 @@
ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper,
null /* icProto */);
setImeWindowStatus(0, mBackDisposition);
- applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The ImeInsetsSourceProvider need the statsToken when dispatching the control. We
+ // send the token here, so that another request in the provider can be cancelled.
+ notifyPreImeWindowVisibilityChanged(false /* visible */, statsToken);
+ } else {
+ applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken);
+ }
mWindowVisible = false;
finishViews(false /* finishingInput */);
if (mDecorViewVisible) {
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 330a9fc..9668b6ad 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -65,10 +65,11 @@
/** The <var>data</var> field holds a number that was
* originally specified in hexadecimal (0xn). */
public static final int TYPE_INT_HEX = 0x11;
- /** The <var>data</var> field holds 0 or 1 that was originally
- * specified as "false" or "true". */
+ /**
+ * {@link #data} holds 0 to represent {@code false}, or a value different from 0 to represent
+ * {@code true}.
+ */
public static final int TYPE_INT_BOOLEAN = 0x12;
-
/** Identifies the start of integer values that were specified as
* color constants (starting with '#'). */
public static final int TYPE_FIRST_COLOR_INT = 0x1c;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 14407ca..762a302 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -30,6 +30,7 @@
import android.view.IWindowId;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.inputmethod.ImeTracker;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.Surface;
@@ -276,7 +277,8 @@
/**
* Updates the requested visible types of insets.
*/
- oneway void updateRequestedVisibleTypes(IWindow window, int requestedVisibleTypes);
+ oneway void updateRequestedVisibleTypes(IWindow window, int requestedVisibleTypes,
+ in @nullable ImeTracker.Token imeStatsToken);
/**
* Called when the system gesture exclusion has changed.
@@ -369,4 +371,14 @@
* @return {@code true} if the focus changes. Otherwise, {@code false}.
*/
boolean moveFocusToAdjacentWindow(IWindow fromWindow, int direction);
+
+ /**
+ * Notifies the statsToken and IME visibility to the ImeInsetsSourceProvider.
+ *
+ * @param window The window that is used to get the ImeInsetsSourceProvider.
+ * @param visible {@code true} to make it visible, {@code false} to hide it.
+ * @param statsToken the token tracking the current IME request.
+ */
+ oneway void notifyImeWindowVisibilityChangedFromClient(IWindow window, boolean visible,
+ in ImeTracker.Token statsToken);
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index f166b89..d83f344 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -121,8 +121,10 @@
* The visibilities should be reported back to WM.
*
* @param types Bitwise flags of types requested visible.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
- void updateRequestedVisibleTypes(@InsetsType int types);
+ void updateRequestedVisibleTypes(@InsetsType int types,
+ @Nullable ImeTracker.Token statsToken);
/**
* @return Whether the host has any callbacks it wants to synchronize the animations with.
@@ -974,6 +976,7 @@
int consumedControlCount = 0;
final @InsetsType int[] showTypes = new int[1];
final @InsetsType int[] hideTypes = new int[1];
+ ImeTracker.Token statsToken = null;
// Ensure to update all existing source consumers
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -988,6 +991,12 @@
if (control != null) {
controllableTypes |= control.getType();
consumedControlCount++;
+
+ if (Flags.refactorInsetsController()) {
+ if (control.getId() == ID_IME) {
+ statsToken = control.getImeStatsToken();
+ }
+ }
}
// control may be null, but we still need to update the control to null if it got
@@ -1021,34 +1030,31 @@
if (Flags.refactorInsetsController()) {
if (mPendingImeControlRequest != null && getImeSourceConsumer().getControl() != null
&& getImeSourceConsumer().getControl().getLeash() != null) {
- // TODO we need to pass the statsToken
- handlePendingControlRequest(null);
+ handlePendingControlRequest(statsToken);
} else {
if (showTypes[0] != 0) {
- applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
- null /* statsToken */);
+ applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken);
}
if (hideTypes[0] != 0) {
- applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
- null /* statsToken */);
+ applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken);
}
}
} else {
if (showTypes[0] != 0) {
- final var statsToken =
+ final var newStatsToken =
(showTypes[0] & ime()) == 0 ? null : ImeTracker.forLogging().onStart(
ImeTracker.TYPE_SHOW, ImeTracker.ORIGIN_CLIENT,
SoftInputShowHideReason.CONTROLS_CHANGED,
mHost.isHandlingPointerEvent() /* fromUser */);
- applyAnimation(showTypes[0], true /* show */, false /* fromIme */, statsToken);
+ applyAnimation(showTypes[0], true /* show */, false /* fromIme */, newStatsToken);
}
if (hideTypes[0] != 0) {
- final var statsToken =
+ final var newStatsToken =
(hideTypes[0] & ime()) == 0 ? null : ImeTracker.forLogging().onStart(
ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT,
SoftInputShowHideReason.CONTROLS_CHANGED,
mHost.isHandlingPointerEvent() /* fromUser */);
- applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, statsToken);
+ applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, newStatsToken);
}
}
@@ -1065,7 +1071,9 @@
}
// InsetsSourceConsumer#setControl might change the requested visibility.
- reportRequestedVisibleTypes();
+ // TODO(b/353463205) check this: if the requestedVisibleTypes for the IME were already
+ // sent, the request would fail. Therefore, don't send the statsToken here.
+ reportRequestedVisibleTypes(null /* statsToken */);
}
@VisibleForTesting(visibility = PACKAGE)
@@ -1176,6 +1184,7 @@
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
if ((Flags.refactorInsetsController() || fromIme) && (typesReady & Type.ime()) != 0) {
+ // TODO(b/353463205) check if this is needed here
ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
}
applyAnimation(typesReady, true /* show */, fromIme, statsToken);
@@ -1243,6 +1252,8 @@
// an animation again (mRequestedVisibleTypes are reported at the end of the IME
// hide animation but set at the beginning)
if ((mRequestedVisibleTypes & ime()) == 0) {
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_ALREADY_HIDDEN);
continue;
}
}
@@ -1346,7 +1357,7 @@
// We are finishing setting the requested visible types. Report them to the server
// and/or the app.
- reportRequestedVisibleTypes();
+ reportRequestedVisibleTypes(statsToken);
}
private void controlAnimationUncheckedInner(@InsetsType int types,
@@ -1396,8 +1407,8 @@
// Ime will not be contained in typesReady nor in controls, if we don't have a leash
Pair<Integer, Integer> typesReadyPair = collectSourceControlsV2(types, controls);
typesReady = typesReadyPair.first;
- @InsetsType int typesWithoutLeash = typesReadyPair.second;
if (animationType == ANIMATION_TYPE_USER) {
+ @InsetsType int typesWithoutLeash = typesReadyPair.second;
// When using an app-driven animation, the IME won't have a leash (because the
// window isn't created yet). If we have a control, but no leash, defers the
// request until the leash gets created.
@@ -1431,6 +1442,11 @@
}
// We need to wait until all types are ready
if (typesReady != types) {
+ if (DEBUG) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "not all types are ready yet, waiting. typesReady: %s, types: %s",
+ typesReady, types));
+ }
return;
}
}
@@ -1728,9 +1744,13 @@
} else {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
- ImeTracker.forLogging().onHidden(statsToken);
+ // The requestedVisibleTypes are only send at the end of the hide animation.
+ // Therefore, the requested is not finished at this point.
+ if (!Flags.refactorInsetsController()) {
+ ImeTracker.forLogging().onHidden(statsToken);
+ }
}
- reportRequestedVisibleTypes();
+ reportRequestedVisibleTypes(shown ? null : runner.getStatsToken());
}
@Override
@@ -1787,7 +1807,7 @@
if (mHost != null) {
// if the (hide) animation is cancelled, the
// requestedVisibleTypes should be reported at this point.
- reportRequestedVisibleTypes();
+ reportRequestedVisibleTypes(control.getStatsToken());
mHost.getInputMethodManager().removeImeSurface(
mHost.getWindowToken());
}
@@ -1923,8 +1943,10 @@
/**
* Called when finishing setting requested visible types or finishing setting controls.
+ *
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
- private void reportRequestedVisibleTypes() {
+ private void reportRequestedVisibleTypes(@Nullable ImeTracker.Token statsToken) {
final @InsetsType int typesToReport;
if (Flags.refactorInsetsController()) {
// If the IME is currently animating out, it is still visible, therefore we only
@@ -1941,8 +1963,23 @@
if (WindowInsets.Type.hasCompatSystemBars(diff)) {
mCompatSysUiVisibilityStaled = true;
}
+ if (Flags.refactorInsetsController()) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ }
mReportedRequestedVisibleTypes = mRequestedVisibleTypes;
- mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes);
+ mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes, statsToken);
+ } else if (Flags.refactorInsetsController()) {
+ if ((typesToReport & ime()) != 0 && mImeSourceConsumer != null) {
+ InsetsSourceControl control = mImeSourceConsumer.getControl();
+ if (control != null && control.getLeash() == null) {
+ // If the IME was requested twice, and we didn't receive the controls
+ // yet, this request will not continue. It should be cancelled here, as
+ // it would time out otherwise.
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ }
+ }
}
updateCompatSysUiVisibility();
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 2efa647..7877352 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -31,6 +31,7 @@
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -56,6 +57,9 @@
private boolean mSkipAnimationOnce;
private int mParcelableFlags;
+ /** The token tracking the current IME request */
+ private @Nullable ImeTracker.Token mImeStatsToken;
+
public InsetsSourceControl(int id, @InsetsType int type, @Nullable SurfaceControl leash,
boolean initiallyVisible, Point surfacePosition, Insets insetsHint) {
mId = id;
@@ -78,6 +82,7 @@
mSurfacePosition = new Point(other.mSurfacePosition);
mInsetsHint = other.mInsetsHint;
mSkipAnimationOnce = other.getAndClearSkipAnimationOnce();
+ mImeStatsToken = other.getImeStatsToken();
}
public InsetsSourceControl(Parcel in) {
@@ -88,6 +93,7 @@
mSurfacePosition = in.readTypedObject(Point.CREATOR);
mInsetsHint = in.readTypedObject(Insets.CREATOR);
mSkipAnimationOnce = in.readBoolean();
+ mImeStatsToken = in.readTypedObject(ImeTracker.Token.CREATOR);
}
public int getId() {
@@ -153,6 +159,15 @@
return result;
}
+ @Nullable
+ public ImeTracker.Token getImeStatsToken() {
+ return mImeStatsToken;
+ }
+
+ public void setImeStatsToken(@Nullable ImeTracker.Token imeStatsToken) {
+ mImeStatsToken = imeStatsToken;
+ }
+
public void setParcelableFlags(int parcelableFlags) {
mParcelableFlags = parcelableFlags;
}
@@ -171,6 +186,7 @@
dest.writeTypedObject(mSurfacePosition, mParcelableFlags);
dest.writeTypedObject(mInsetsHint, mParcelableFlags);
dest.writeBoolean(mSkipAnimationOnce);
+ dest.writeTypedObject(mImeStatsToken, mParcelableFlags);
}
public void release(Consumer<SurfaceControl> surfaceReleaseConsumer) {
@@ -196,13 +212,14 @@
&& mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
- && mSkipAnimationOnce == that.mSkipAnimationOnce;
+ && mSkipAnimationOnce == that.mSkipAnimationOnce
+ && Objects.equals(mImeStatsToken, that.mImeStatsToken);
}
@Override
public int hashCode() {
return Objects.hash(mId, mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
- mSkipAnimationOnce);
+ mSkipAnimationOnce, mImeStatsToken);
}
@Override
@@ -225,6 +242,7 @@
pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
pw.print(" mInsetsHint="); pw.print(mInsetsHint);
pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce);
+ pw.print(" mImeStatsToken="); pw.print(mImeStatsToken);
pw.println();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f204f9..37d5220 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10057,6 +10057,24 @@
}
/**
+ * Dispatches the statsToken and IME visibility to the ImeInsetsSourceProvider.
+ *
+ * @param visible {@code true} if it became visible, {@code false} otherwise.
+ * @param statsToken the token tracking the current IME request.
+ *
+ * @hide
+ */
+ public void notifyImeVisibilityChanged(boolean visible, @NonNull ImeTracker.Token statsToken) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_NOTIFY_IME_VISIBILITY_CHANGED);
+ try {
+ mWindowSession.notifyImeWindowVisibilityChangedFromClient(mWindow, visible, statsToken);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Represents a pending input event that is waiting in a queue.
*
* Input events are processed in serial order by the timestamp specified by
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index b66c59a..889acca4 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -19,12 +19,14 @@
import static android.view.InsetsController.DEBUG;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import java.util.List;
@@ -151,10 +153,17 @@
}
@Override
- public void updateRequestedVisibleTypes(@WindowInsets.Type.InsetsType int types) {
+ public void updateRequestedVisibleTypes(@WindowInsets.Type.InsetsType int types,
+ @Nullable ImeTracker.Token statsToken) {
try {
if (mViewRoot.mAdded) {
- mViewRoot.mWindowSession.updateRequestedVisibleTypes(mViewRoot.mWindow, types);
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_UPDATE_REQUESTED_VISIBLE_TYPES);
+ mViewRoot.mWindowSession.updateRequestedVisibleTypes(mViewRoot.mWindow, types,
+ statsToken);
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_UPDATE_REQUESTED_VISIBLE_TYPES);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to call insetsModified", e);
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 7871858..0d027f1 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -30,6 +30,7 @@
import android.util.MergedConfiguration;
import android.view.View.FocusDirection;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
import android.window.InputTransferToken;
import android.window.OnBackInvokedCallbackInfo;
@@ -596,7 +597,7 @@
@Override
public void updateRequestedVisibleTypes(IWindow window,
- @InsetsType int requestedVisibleTypes) {
+ @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken) {
}
@Override
@@ -677,6 +678,11 @@
return false;
}
+ @Override
+ public void notifyImeWindowVisibilityChangedFromClient(IWindow window, boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
+ }
+
void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index edc9921..b9751c8 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -202,6 +202,24 @@
PHASE_IME_HIDE_WINDOW,
PHASE_IME_PRIVILEGED_OPERATIONS,
PHASE_SERVER_CURRENT_ACTIVE_IME,
+ PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES,
+ PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY,
+ PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED,
+ PHASE_CLIENT_HANDLE_DISPATCH_IME_VISIBILITY_CHANGED,
+ PHASE_CLIENT_NOTIFY_IME_VISIBILITY_CHANGED,
+ PHASE_CLIENT_UPDATE_REQUESTED_VISIBLE_TYPES,
+ PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SET_REQUESTED_VISIBILITY,
+ PHASE_WM_GET_CONTROL_WITH_LEASH,
+ PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES,
+ PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW,
+ PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY,
+ PHASE_CLIENT_SET_IME_VISIBILITY,
+ PHASE_WM_DISPATCH_IME_REQUESTED_CHANGED,
+ PHASE_CLIENT_NO_ONGOING_USER_ANIMATION,
+ PHASE_WM_NOTIFY_IME_VISIBILITY_CHANGED_FROM_CLIENT,
+ PHASE_WM_POSTING_CHANGED_IME_VISIBILITY,
+ PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
+ PHASE_CLIENT_ALREADY_HIDDEN,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -351,6 +369,62 @@
/** Checked that the calling IME is the currently active IME. */
int PHASE_SERVER_CURRENT_ACTIVE_IME = ImeProtoEnums.PHASE_SERVER_CURRENT_ACTIVE_IME;
+ /** Reporting the new requested visible types. */
+ int PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES =
+ ImeProtoEnums.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES;
+ /** Setting the IME visibility for the RemoteInsetsControlTarget. */
+ int PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY =
+ ImeProtoEnums.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY;
+ /** IME has no insets pending and is server visible. Notify about changed controls. */
+ int PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED =
+ ImeProtoEnums.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED;
+ /** Handling the dispatch of the IME visibility change. */
+ int PHASE_CLIENT_HANDLE_DISPATCH_IME_VISIBILITY_CHANGED =
+ ImeProtoEnums.PHASE_CLIENT_HANDLE_DISPATCH_IME_VISIBILITY_CHANGED;
+ /** Dispatching the IME visibility change. */
+ int PHASE_CLIENT_NOTIFY_IME_VISIBILITY_CHANGED =
+ ImeProtoEnums.PHASE_CLIENT_NOTIFY_IME_VISIBILITY_CHANGED;
+ /** Updating the requested visible types. */
+ int PHASE_CLIENT_UPDATE_REQUESTED_VISIBLE_TYPES =
+ ImeProtoEnums.PHASE_CLIENT_UPDATE_REQUESTED_VISIBLE_TYPES;
+ /** Reached the remote insets control target's setImeInputTargetRequestedVisibility method. */
+ int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SET_REQUESTED_VISIBILITY =
+ ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SET_REQUESTED_VISIBILITY;
+ /** Received a new insets source control with a leash. */
+ int PHASE_WM_GET_CONTROL_WITH_LEASH =
+ ImeProtoEnums.PHASE_WM_GET_CONTROL_WITH_LEASH;
+ /**
+ * Updating the requested visible types in the WindowState and sending them to state
+ * controller.
+ */
+ int PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES =
+ ImeProtoEnums.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES;
+ /** Setting the requested IME visibility of a window. */
+ int PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW =
+ ImeProtoEnums.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW;
+ /** Reached the redirect of InputMethodManager to InsetsController show/hide. */
+ int PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY =
+ ImeProtoEnums.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY;
+ /** Reached the InputMethodManager Handler call to send the visibility. */
+ int PHASE_CLIENT_SET_IME_VISIBILITY = ImeProtoEnums.PHASE_CLIENT_SET_IME_VISIBILITY;
+ /** Calling into the listener to show/hide the IME from the ImeInsetsSourceProvider. */
+ int PHASE_WM_DISPATCH_IME_REQUESTED_CHANGED =
+ ImeProtoEnums.PHASE_WM_DISPATCH_IME_REQUESTED_CHANGED;
+ /** An ongoing user animation will not be interrupted by a IMM#showSoftInput. */
+ int PHASE_CLIENT_NO_ONGOING_USER_ANIMATION =
+ ImeProtoEnums.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION;
+ /** Dispatching the token to the ImeInsetsSourceProvider. */
+ int PHASE_WM_NOTIFY_IME_VISIBILITY_CHANGED_FROM_CLIENT =
+ ImeProtoEnums.PHASE_WM_NOTIFY_IME_VISIBILITY_CHANGED_FROM_CLIENT;
+ /** Now posting the IME visibility to the WMS handler. */
+ int PHASE_WM_POSTING_CHANGED_IME_VISIBILITY =
+ ImeProtoEnums.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY;
+ /** Inside the WMS handler calling into the listener that calls into IMMS show/hide. */
+ int PHASE_WM_INVOKING_IME_REQUESTED_LISTENER =
+ ImeProtoEnums.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER;
+ /** IME is requested to be hidden, but already hidden. Don't hide to avoid another animation. */
+ int PHASE_CLIENT_ALREADY_HIDDEN = ImeProtoEnums.PHASE_CLIENT_ALREADY_HIDDEN;
+
/**
* Called when an IME request is started.
*
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index fed8eea..d40b72c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1350,11 +1350,15 @@
return;
}
case MSG_SET_VISIBILITY:
- final boolean visible = msg.arg1 != 0;
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final boolean visible = (boolean) args.arg1;
+ final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg2;
synchronized (mH) {
if (mCurRootView != null) {
final var insetsController = mCurRootView.getInsetsController();
if (insetsController != null) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY);
if (visible) {
insetsController.show(WindowInsets.Type.ime(),
false /* fromIme */, null /* statsToken */);
@@ -1363,6 +1367,9 @@
false /* fromIme */, null /* statsToken */);
}
}
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_CLIENT_HANDLE_SET_IME_VISIBILITY);
}
}
break;
@@ -1463,8 +1470,13 @@
}
@Override
- public void setImeVisibility(boolean visible) {
- mH.obtainMessage(MSG_SET_VISIBILITY, visible ? 1 : 0, 0).sendToTarget();
+ public void setImeVisibility(boolean visible, @Nullable ImeTracker.Token statsToken) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = visible;
+ args.arg2 = statsToken;
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_SET_IME_VISIBILITY);
+ mH.obtainMessage(MSG_SET_VISIBILITY, args).sendToTarget();
}
@Override
@@ -2344,11 +2356,15 @@
if (viewRootImpl != null
&& (viewRootImpl.getInsetsController().computeUserAnimatingTypes()
& WindowInsets.Type.ime()) == 0) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION);
// TODO(b/322992891) handle case of SHOW_IMPLICIT
viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(),
false /* fromIme */, statsToken);
return true;
}
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION);
return false;
} else {
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index c451cc8..7f48c42 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -80,14 +80,6 @@
}
flag {
- name: "introduce_smoother_dimmer"
- namespace: "windowing_frontend"
- description: "Refactor dim to fix flickers"
- bug: "295291019"
- is_fixed_read_only: true
-}
-
-flag {
name: "transit_ready_tracking"
namespace: "windowing_frontend"
description: "Enable accurate transition readiness tracking"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 13d465f..f8a2a31 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -42,14 +42,6 @@
flag {
namespace: "windowing_sdk"
- name: "activity_window_info_flag"
- description: "To dispatch ActivityWindowInfo through ClientTransaction"
- bug: "287582673"
- is_fixed_read_only: true
-}
-
-flag {
- namespace: "windowing_sdk"
name: "untrusted_embedding_state_sharing"
is_exported: true
description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
index 6a7fa99..6eb9d2e 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
@@ -16,6 +16,7 @@
package com.android.internal.inputmethod;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.inputmethod.InputBindResult;
/**
@@ -30,7 +31,7 @@
void onUnbindAccessibilityService(int sequence, int id);
void setActive(boolean active, boolean fullscreen);
void setInteractive(boolean active, boolean fullscreen);
- void setImeVisibility(boolean visible);
+ void setImeVisibility(boolean visible, in @nullable ImeTracker.Token statsToken);
void scheduleStartInputIfNecessary(boolean fullscreen);
void reportFullscreenMode(boolean fullscreen);
void setImeTraceEnabled(boolean enabled);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 011ef30..ca984c0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -117,7 +117,6 @@
],
static_libs: [
- "libnativehelper_lazy",
"libziparchive_for_incfs",
"libguiflags",
],
@@ -292,6 +291,7 @@
"libgif",
"libgui_window_info_static",
"libkernelconfigs",
+ "libnativehelper_lazy",
"libseccomp_policy",
"libgrallocusage",
"libscrypt_static",
@@ -421,6 +421,7 @@
"libimage_type_recognition",
"libinput",
"libjpeg",
+ "libnativehelper_jvm",
"libpiex",
"libpng",
"libtiff_directory",
diff --git a/core/res/Android.bp b/core/res/Android.bp
index a44e92c..9207aa8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -163,6 +163,7 @@
"android.net.platform.flags-aconfig",
"com.android.window.flags.window-aconfig",
"android.permission.flags-aconfig",
+ "android.os.flags-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9f00d5e..193836e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8243,7 +8243,8 @@
<p>Not for use by third-party applications. -->
<permission
android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.os.allow_consentless_bugreport_delegated_consent" />
<!-- @SystemApi Allows to call APIs that log process lifecycle events
@hide -->
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 72c4639..a556c0d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -43,8 +43,8 @@
import android.window.ClientWindowFrames;
import android.window.WindowContextInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index f9609fc..31a4f16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -50,8 +50,8 @@
import android.window.ActivityWindowInfo;
import android.window.WindowTokenClient;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 79659ca..e559b05 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -23,8 +23,8 @@
import android.app.ClientTransactionHandler;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index eb69b9c..024df1e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -53,8 +53,8 @@
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index a4d7661..81291ff 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -39,8 +39,8 @@
import android.platform.test.annotations.Presubmit;
import android.window.ActivityWindowInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ae7f465..7bc0d2f 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -56,6 +56,7 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Point;
@@ -1095,9 +1096,10 @@
}
@Override
- public void updateRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ public void updateRequestedVisibleTypes(@InsetsType int requestedVisibleTypes,
+ @Nullable ImeTracker.Token statsToken) {
mRequestedVisibleTypes = requestedVisibleTypes;
- super.updateRequestedVisibleTypes(requestedVisibleTypes);
+ super.updateRequestedVisibleTypes(requestedVisibleTypes, statsToken);
}
public boolean isRequestedVisible(@InsetsType int types) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
new file mode 100644
index 0000000..bfe1306a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
@@ -0,0 +1,6 @@
+atsjenk@google.com
+jorgegil@google.com
+madym@google.com
+pbdr@google.com
+tkachenkoi@google.com
+vaniadesmonda@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index f4ac5f2..4fbb574 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -344,8 +344,7 @@
if (android.view.inputmethod.Flags.refactorInsetsController()) {
if (pendingImeStartAnimation) {
- startAnimation(true, true /* forceRestart */,
- null /* statsToken */);
+ startAnimation(true, true /* forceRestart */);
}
}
}
@@ -398,8 +397,7 @@
// already (e.g., when focussing an editText in activity B, while and editText in
// activity A is focussed), we will not get a call of #insetsControlChanged, and
// therefore have to start the show animation from here
- startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */,
- null /* TODO statsToken */);
+ startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */);
}
}
@@ -436,16 +434,31 @@
.navBarFrameHeight();
}
+ private void startAnimation(final boolean show, final boolean forceRestart) {
+ final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ if (imeSource == null || mImeSourceControl == null) {
+ return;
+ }
+ final var statsToken = mImeSourceControl.getImeStatsToken();
+
+ startAnimation(show, forceRestart, statsToken);
+ }
+
private void startAnimation(final boolean show, final boolean forceRestart,
@SoftInputShowHideReason int reason) {
final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
if (imeSource == null || mImeSourceControl == null) {
return;
}
- final var statsToken = ImeTracker.forLogging().onStart(
- show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL,
- reason, false /* fromUser */);
-
+ final ImeTracker.Token statsToken;
+ if (android.view.inputmethod.Flags.refactorInsetsController()
+ && mImeSourceControl.getImeStatsToken() != null) {
+ statsToken = mImeSourceControl.getImeStatsToken();
+ } else {
+ statsToken = ImeTracker.forLogging().onStart(
+ show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_WM_SHELL, reason, false /* fromUser */);
+ }
startAnimation(show, forceRestart, statsToken);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 1931212..cb087a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -23,6 +23,8 @@
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
@@ -91,7 +93,8 @@
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
- if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ && Flags.enableWindowingDynamicInitialBounds()) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
}
@@ -150,7 +153,8 @@
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
- if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+ && Flags.enableWindowingDynamicInitialBounds()) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 32526ff..8f587d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.dagger;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
+
import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.Context;
@@ -30,7 +32,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
-import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -567,7 +568,7 @@
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
ShellTaskOrganizer shellTaskOrganizer) {
if (!DesktopModeStatus.canEnterDesktopMode(context)
- || !Flags.enableDesktopWindowingTaskLimit()) {
+ || DESKTOP_WINDOWING_MODE.isEnabled(context)) {
return Optional.empty();
}
return Optional.of(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9fd2c27..886609a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -67,6 +67,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -786,7 +787,7 @@
moveHomeTask(wct, toTop = true)
// Currently, we only handle the desktop on the default display really.
- if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
+ if (displayId == DEFAULT_DISPLAY && WALLPAPER_ACTIVITY.isEnabled(context)) {
// Add translucent wallpaper activity to show the wallpaper underneath
addWallpaperActivity(wct)
}
@@ -974,7 +975,7 @@
&& isTopActivityExemptFromDesktopWindowing(context, task)
private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
- return Flags.enableDesktopWindowingWallpaperActivity() &&
+ return WALLPAPER_ACTIVITY.isEnabled(context) &&
TransitionUtil.isClosingType(request.type) &&
request.triggerTask != null
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 246fd92..74e53fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,9 +23,9 @@
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -36,7 +36,7 @@
* mode and other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
- context: Context,
+ private val context: Context,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -79,7 +79,7 @@
}
private fun updateWallpaperToken(info: TransitionInfo) {
- if (!enableDesktopWindowingWallpaperActivity()) {
+ if (!WALLPAPER_ACTIVITY.isEnabled(context)) {
return
}
info.changes.forEach { change ->
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
new file mode 100644
index 0000000..c847710
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MinimizeWindowOnAppOpen.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing the least recently used window when a new window is opened
+ * above the window limit. For tangor devices, which this test currently runs on, the window limit
+ * is 4.
+ */
+@Ignore("Base Test Class")
+abstract class MinimizeWindowOnAppOpen()
+{
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+ private val letterboxAppHelper = DesktopModeAppHelper(LetterboxAppHelper(instrumentation))
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun openAppToMinimizeWindow() {
+ // Launch a new app while 4 apps are already open on desktop. This should result in the
+ // first app we opened to be minimized.
+ letterboxAppHelper.launchViaIntent(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ letterboxAppHelper.exit(wmHelper)
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 8571360..0ed0386 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -25,6 +25,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArraySet;
+import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.Nullable;
@@ -33,6 +34,7 @@
import java.util.List;
public final class BatteryUtils {
+ private static final String TAG = "BatteryUtils";
/** The key to get the time to full from Settings.Global */
public static final String GLOBAL_TIME_TO_FULL_MILLIS = "time_to_full_millis";
@@ -89,6 +91,18 @@
return userManager.isPrivateProfile();
}
+ /** Returns true if current user is an additional profile user. */
+ public static boolean isAdditionalProfile(Context context) {
+ if (isWorkProfile(context)) {
+ Log.d(TAG, "Current user is a work profile user.");
+ return true;
+ } else if (isPrivateProfile(context)) {
+ Log.d(TAG, "Current user is a private profile user.");
+ return true;
+ }
+ return false;
+ }
+
private static Boolean sChargingStringV2Enabled = null;
/** Returns {@code true} if the charging string v2 is enabled. */
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
index 465798c..4aec0d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatteryUtilsTest.java
@@ -127,6 +127,25 @@
assertThat(BatteryUtils.isPrivateProfile(mContext)).isTrue();
}
+ @Test
+ public void isAdditionalProfile_defaultValue_returnFalse() {
+ assertThat(BatteryUtils.isAdditionalProfile(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAdditionalProfile_workProfileMode_returnTrue() {
+ doReturn(true).when(mUserManager).isManagedProfile();
+
+ assertThat(BatteryUtils.isAdditionalProfile(mContext)).isTrue();
+ }
+
+ @Test
+ public void isAdditionalProfile_privateProfileMode_returnTrue() {
+ doReturn(true).when(mUserManager).isPrivateProfile();
+
+ assertThat(BatteryUtils.isAdditionalProfile(mContext)).isTrue();
+ }
+
private void setTtsPackageName(String defaultTtsPackageName) {
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_SYNTH, defaultTtsPackageName);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4e01a71..1c3515a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -570,6 +570,7 @@
"jsr305",
"jsr330",
"lottie",
+ "lottie_compose",
"LowLightDreamLib",
"TraceurCommon",
"//frameworks/libs/systemui:motion_tool_lib",
@@ -727,6 +728,7 @@
"truth",
"monet",
"libmonet",
+ "lottie_compose",
"dagger2",
"jsr330",
"WindowManager-Shell",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 0cd4b68..c0832d9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -60,7 +60,7 @@
private val clockInteractor: KeyguardClockInteractor,
) {
@Composable
- fun DefaultClockLayout(
+ fun SceneScope.DefaultClockLayout(
modifier: Modifier = Modifier,
) {
val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -95,26 +95,29 @@
}
}
- SceneTransitionLayout(state, modifier) {
- scene(splitShadeLargeClockScene) {
- LargeClockWithSmartSpace(
- shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
- )
+ Column(modifier) {
+ SceneTransitionLayout(state) {
+ scene(splitShadeLargeClockScene) {
+ LargeClockWithSmartSpace(
+ shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+ )
+ }
+
+ scene(splitShadeSmallClockScene) {
+ SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ }
+
+ scene(smallClockScene) { SmallClockWithSmartSpace() }
+
+ scene(largeClockScene) { LargeClockWithSmartSpace() }
+
+ scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
+
+ scene(WeatherClockScenes.splitShadeLargeClockScene) {
+ WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ }
}
-
- scene(splitShadeSmallClockScene) {
- SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
- }
-
- scene(smallClockScene) { SmallClockWithSmartSpace() }
-
- scene(largeClockScene) { LargeClockWithSmartSpace() }
-
- scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
-
- scene(WeatherClockScenes.splitShadeLargeClockScene) {
- WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
- }
+ with(mediaCarouselSection) { KeyguardMediaCarousel() }
}
}
@@ -136,7 +139,6 @@
onTopChanged = burnIn.onSmartspaceTopChanged,
)
}
- with(mediaCarouselSection) { KeyguardMediaCarousel() }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
index a22bc34..0bd0809 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -42,22 +42,8 @@
toSceneZIndex: Float
): SceneKey? {
return when {
- // TODO: 352052894 - update with the actual scene picking
- transition.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) -> {
- if (transition.progress < SHADE_FRACTION) {
- Scenes.Lockscreen
- } else {
- Scenes.Shade
- }
- }
-
- // TODO: 345467290 - update with the actual scene picking
- transition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) -> {
- if (transition.progress < 1f - SHADE_FRACTION) {
- Scenes.Shade
- } else {
- Scenes.Lockscreen
- }
+ shouldElevateMedia(transition) -> {
+ Scenes.Shade
}
// TODO: 345467290 - update with the actual scene picking
@@ -69,4 +55,12 @@
else -> pickSingleSceneIn(scenes, transition, element)
}
}
+
+ /** Returns true when the media should be laid on top of the rest for the given [transition]. */
+ fun shouldElevateMedia(transition: TransitionState.Transition?): Boolean {
+ if (transition == null) {
+ return false
+ }
+ return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ece8b40..09414a9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -80,6 +80,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -360,7 +361,13 @@
maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
layout(constraints.maxWidth, constraints.maxHeight) {
- quickSettingsPlaceable.placeRelative(x = 0, y = 0)
+ val qsZIndex =
+ if (MediaScenePicker.shouldElevateMedia(layoutState.currentTransition)) {
+ 1f
+ } else {
+ 0f
+ }
+ quickSettingsPlaceable.placeRelative(x = 0, y = 0, zIndex = qsZIndex)
notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 377b02b..3ad07d0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -1133,10 +1133,98 @@
val transformation =
transformation(transition.transformationSpec.transformations(element.key, scene))
- // If there is no transformation explicitly associated to this element value, let's use
- // the value given by the system (like the current position and size given by the layout
- // pass).
- ?: return currentValue()
+
+ val previewTransformation =
+ transition.previewTransformationSpec?.let {
+ transformation(it.transformations(element.key, scene))
+ }
+ if (previewTransformation != null) {
+ val isInPreviewStage = transition.isInPreviewStage
+
+ val idleValue = sceneValue(sceneState)
+ val isEntering = scene == toScene
+ val previewTargetValue =
+ previewTransformation.transform(
+ layoutImpl,
+ scene,
+ element,
+ sceneState,
+ transition,
+ idleValue,
+ )
+
+ val targetValueOrNull =
+ transformation?.transform(
+ layoutImpl,
+ scene,
+ element,
+ sceneState,
+ transition,
+ idleValue,
+ )
+
+ // Make sure we don't read progress if values are the same and we don't need to interpolate,
+ // so we don't invalidate the phase where this is read.
+ when {
+ isInPreviewStage && isEntering && previewTargetValue == targetValueOrNull ->
+ return previewTargetValue
+ isInPreviewStage && !isEntering && idleValue == previewTargetValue -> return idleValue
+ previewTargetValue == targetValueOrNull && idleValue == previewTargetValue ->
+ return idleValue
+ else -> {}
+ }
+
+ val previewProgress = transition.previewProgress
+ // progress is not needed for all cases of the below when block, therefore read it lazily
+ // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range
+ val previewRangeProgress =
+ previewTransformation.range?.progress(previewProgress) ?: previewProgress
+
+ if (isInPreviewStage) {
+ // if we're in the preview stage of the transition, interpolate between start state and
+ // preview target state:
+ return if (isEntering) {
+ // i.e. in the entering case between previewTargetValue and targetValue (or
+ // idleValue if no transformation is defined in the second stage transition)...
+ lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress)
+ } else {
+ // ...and in the exiting case between the idleValue and the previewTargetValue.
+ lerp(idleValue, previewTargetValue, previewRangeProgress)
+ }
+ }
+
+ // if we're in the second stage of the transition, interpolate between the state the
+ // element was left at the end of the preview-phase and the target state:
+ return if (isEntering) {
+ // i.e. in the entering case between preview-end-state and the idleValue...
+ lerp(
+ lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress),
+ idleValue,
+ transformation?.range?.progress(transition.progress) ?: transition.progress
+ )
+ } else {
+ if (targetValueOrNull == null) {
+ // ... and in the exiting case, the element should remain in the preview-end-state
+ // if no further transformation is defined in the second-stage transition...
+ lerp(idleValue, previewTargetValue, previewRangeProgress)
+ } else {
+ // ...and otherwise it should be interpolated between preview-end-state and
+ // targetValue
+ lerp(
+ lerp(idleValue, previewTargetValue, previewRangeProgress),
+ targetValueOrNull,
+ transformation.range?.progress(transition.progress) ?: transition.progress
+ )
+ }
+ }
+ }
+
+ if (transformation == null) {
+ // If there is no transformation explicitly associated to this element value, let's use
+ // the value given by the system (like the current position and size given by the layout
+ // pass).
+ return currentValue()
+ }
val idleValue = sceneValue(sceneState)
val targetValue =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index d3e2a1c..fd6762b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -77,8 +77,17 @@
private var progressAnimatable by mutableStateOf<Animatable<Float, AnimationVector1D>?>(null)
var dragProgress: Float by mutableFloatStateOf(0f)
+ override val previewProgress: Float
+ get() = dragProgress
+
+ override val previewProgressVelocity: Float
+ get() = 0f // Currently, velocity is not exposed by predictive back API
+
+ override val isInPreviewStage: Boolean
+ get() = progressAnimatable == null && previewTransformationSpec != null
+
override val progress: Float
- get() = progressAnimatable?.value ?: dragProgress
+ get() = progressAnimatable?.value ?: previewTransformationSpec?.let { 0f } ?: dragProgress
override val progressVelocity: Float
get() = progressAnimatable?.velocity ?: 0f
@@ -109,8 +118,8 @@
toScene -> 1f
else -> error("scene $currentScene should be either $fromScene or $toScene")
}
-
- val animatable = Animatable(dragProgress).also { progressAnimatable = it }
+ val startProgress = if (previewTransformationSpec != null) 0f else dragProgress
+ val animatable = Animatable(startProgress).also { progressAnimatable = it }
// Important: We start atomically to make sure that we start the coroutine even if it is
// cancelled right after it is launched, so that finishTransition() is correctly called.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 48bc251..08e8e72 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -189,6 +189,19 @@
/** The current velocity of [progress], in progress units. */
abstract val progressVelocity: Float
+ /**
+ * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
+ * also be less than `0` or greater than `1` when using transitions with a spring
+ * AnimationSpec or when flinging quickly during a swipe gesture.
+ */
+ open val previewProgress: Float = 0f
+
+ /** The current velocity of [previewProgress], in progress units. */
+ open val previewProgressVelocity: Float = 0f
+
+ /** Whether the transition is currently in the preview stage */
+ open val isInPreviewStage: Boolean = false
+
/** Whether the transition was triggered by user input rather than being programmatic. */
abstract val isInitiatedByUserInput: Boolean
@@ -203,6 +216,7 @@
* [started][MutableSceneTransitionLayoutStateImpl.startTransition].
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ internal var previewTransformationSpec: TransformationSpecImpl? = null
private var fromOverscrollSpec: OverscrollSpecImpl? = null
private var toOverscrollSpec: OverscrollSpecImpl? = null
@@ -437,6 +451,10 @@
transitions
.transitionSpec(fromScene, toScene, key = transition.key)
.transformationSpec()
+ transition.previewTransformationSpec =
+ transitions
+ .transitionSpec(fromScene, toScene, key = transition.key)
+ .previewTransformationSpec()
if (orientation != null) {
transition.updateOverscrollSpecs(
fromSpec = transitions.overscrollSpec(fromScene, orientation),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index e30dd356..06b093d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -110,7 +110,7 @@
}
private fun defaultTransition(from: SceneKey, to: SceneKey) =
- TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
+ TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
overscrollCache
@@ -177,10 +177,18 @@
/**
* The [TransformationSpec] associated to this [TransitionSpec].
*
- * Note that this is called once every a transition associated to this [TransitionSpec] is
+ * Note that this is called once whenever a transition associated to this [TransitionSpec] is
* started.
*/
fun transformationSpec(): TransformationSpec
+
+ /**
+ * The preview [TransformationSpec] associated to this [TransitionSpec].
+ *
+ * Note that this is called once whenever a transition associated to this [TransitionSpec] is
+ * started.
+ */
+ fun previewTransformationSpec(): TransformationSpec?
}
interface TransformationSpec {
@@ -225,13 +233,17 @@
override val key: TransitionKey?,
override val from: SceneKey?,
override val to: SceneKey?,
- private val transformationSpec: () -> TransformationSpecImpl,
+ private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null,
+ private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null,
+ private val transformationSpec: () -> TransformationSpecImpl
) : TransitionSpec {
override fun reversed(): TransitionSpecImpl {
return TransitionSpecImpl(
key = key,
from = to,
to = from,
+ previewTransformationSpec = reversePreviewTransformationSpec,
+ reversePreviewTransformationSpec = previewTransformationSpec,
transformationSpec = {
val reverse = transformationSpec.invoke()
TransformationSpecImpl(
@@ -245,6 +257,9 @@
}
override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
+
+ override fun previewTransformationSpec(): TransformationSpecImpl? =
+ previewTransformationSpec?.invoke()
}
/** The definition of the overscroll behavior of the [scene]. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 89ed8d6..3a87d41 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -54,11 +54,17 @@
* If [key] is not `null`, then this transition will only be used if the same key is specified
* when triggering the transition.
*
+ * Optionally, define a [preview] animation which will be played during the first stage of the
+ * transition, e.g. during the predictive back gesture. In case your transition should be
+ * reversible with the reverse animation having a preview as well, define a [reversePreview].
+ *
* @see from
*/
fun to(
to: SceneKey,
key: TransitionKey? = null,
+ preview: (TransitionBuilder.() -> Unit)? = null,
+ reversePreview: (TransitionBuilder.() -> Unit)? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
@@ -74,11 +80,17 @@
* 2. to == A && from == B, which is then treated in reverse.
* 3. (from == A && to == null) || (from == null && to == B)
* 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse.
+ *
+ * Optionally, define a [preview] animation which will be played during the first stage of the
+ * transition, e.g. during the predictive back gesture. In case your transition should be
+ * reversible with the reverse animation having a preview as well, define a [reversePreview].
*/
fun from(
from: SceneKey,
to: SceneKey? = null,
key: TransitionKey? = null,
+ preview: (TransitionBuilder.() -> Unit)? = null,
+ reversePreview: (TransitionBuilder.() -> Unit)? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 1e67aa9..02a4362 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -62,18 +62,22 @@
override fun to(
to: SceneKey,
key: TransitionKey?,
+ preview: (TransitionBuilder.() -> Unit)?,
+ reversePreview: (TransitionBuilder.() -> Unit)?,
builder: TransitionBuilder.() -> Unit
): TransitionSpec {
- return transition(from = null, to = to, key = key, builder)
+ return transition(from = null, to = to, key = key, preview, reversePreview, builder)
}
override fun from(
from: SceneKey,
to: SceneKey?,
key: TransitionKey?,
+ preview: (TransitionBuilder.() -> Unit)?,
+ reversePreview: (TransitionBuilder.() -> Unit)?,
builder: TransitionBuilder.() -> Unit
): TransitionSpec {
- return transition(from = from, to = to, key = key, builder)
+ return transition(from = from, to = to, key = key, preview, reversePreview, builder)
}
override fun overscroll(
@@ -103,9 +107,11 @@
from: SceneKey?,
to: SceneKey?,
key: TransitionKey?,
+ preview: (TransitionBuilder.() -> Unit)?,
+ reversePreview: (TransitionBuilder.() -> Unit)?,
builder: TransitionBuilder.() -> Unit,
): TransitionSpec {
- fun transformationSpec(): TransformationSpecImpl {
+ fun transformationSpec(builder: TransitionBuilder.() -> Unit): TransformationSpecImpl {
val impl = TransitionBuilderImpl().apply(builder)
return TransformationSpecImpl(
progressSpec = impl.spec,
@@ -115,7 +121,18 @@
)
}
- val spec = TransitionSpecImpl(key, from, to, ::transformationSpec)
+ val previewTransformationSpec = preview?.let { { transformationSpec(it) } }
+ val reversePreviewTransformationSpec = reversePreview?.let { { transformationSpec(it) } }
+ val transformationSpec = { transformationSpec(builder) }
+ val spec =
+ TransitionSpecImpl(
+ key,
+ from,
+ to,
+ previewTransformationSpec,
+ reversePreviewTransformationSpec,
+ transformationSpec
+ )
transitionSpecs.add(spec)
return spec
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index c91151e..1d9e9b7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2181,4 +2181,165 @@
rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp)
}
+
+ @Test
+ fun previewInterpolation_previewStage() {
+ val exiting1 = ElementKey("exiting1")
+ val exiting2 = ElementKey("exiting2")
+ val exiting3 = ElementKey("exiting3")
+ val entering1 = ElementKey("entering1")
+ val entering2 = ElementKey("entering2")
+ val entering3 = ElementKey("entering3")
+
+ val layoutImpl =
+ testPreviewTransformation(
+ from = SceneB,
+ to = SceneA,
+ exitingElements = listOf(exiting1, exiting2, exiting3),
+ enteringElements = listOf(entering1, entering2, entering3),
+ preview = {
+ scaleDraw(exiting1, scaleX = 0.8f, scaleY = 0.8f)
+ translate(exiting2, x = 20.dp)
+ scaleDraw(entering1, scaleX = 0f, scaleY = 0f)
+ translate(entering2, y = 30.dp)
+ },
+ transition = {
+ translate(exiting2, x = 30.dp)
+ scaleSize(exiting3, width = 0.8f, height = 0.8f)
+ scaleDraw(entering1, scaleX = 0.5f, scaleY = 0.5f)
+ scaleSize(entering3, width = 0.2f, height = 0.2f)
+ },
+ previewProgress = 0.5f,
+ progress = 0f,
+ isInPreviewStage = true
+ )
+
+ // verify that preview transition for exiting elements is halfway played from
+ // current-scene-value -> preview-target-value
+ val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+ // e.g. exiting1 is half scaled...
+ assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
+ // ...and exiting2 is halfway translated from 0.dp to 20.dp...
+ rule.onNode(isElement(exiting2)).assertPositionInRootIsEqualTo(10.dp, 0.dp)
+ // ...whereas exiting3 remains in its original size because it is only affected by the
+ // second phase of the transition
+ rule.onNode(isElement(exiting3)).assertSizeIsEqualTo(100.dp, 100.dp)
+
+ // verify that preview transition for entering elements is halfway played from
+ // preview-target-value -> transition-target-value (or target-scene-value if no
+ // transition-target-value defined).
+ val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+ // e.g. entering1 is half scaled between 0f and 0.5f -> 0.25f...
+ assertThat(entering1InA.lastScale).isEqualTo(Scale(0.25f, 0.25f, Offset.Unspecified))
+ // ...and entering2 is half way translated between 30.dp and 0.dp
+ rule.onNode(isElement(entering2)).assertPositionInRootIsEqualTo(0.dp, 15.dp)
+ // ...and entering3 is still at its start size of 0.2f * 100.dp, because it is unaffected
+ // by the preview phase
+ rule.onNode(isElement(entering3)).assertSizeIsEqualTo(20.dp, 20.dp)
+ }
+
+ @Test
+ fun previewInterpolation_transitionStage() {
+ val exiting1 = ElementKey("exiting1")
+ val exiting2 = ElementKey("exiting2")
+ val exiting3 = ElementKey("exiting3")
+ val entering1 = ElementKey("entering1")
+ val entering2 = ElementKey("entering2")
+ val entering3 = ElementKey("entering3")
+
+ val layoutImpl =
+ testPreviewTransformation(
+ from = SceneB,
+ to = SceneA,
+ exitingElements = listOf(exiting1, exiting2, exiting3),
+ enteringElements = listOf(entering1, entering2, entering3),
+ preview = {
+ scaleDraw(exiting1, scaleX = 0.8f, scaleY = 0.8f)
+ translate(exiting2, x = 20.dp)
+ scaleDraw(entering1, scaleX = 0f, scaleY = 0f)
+ translate(entering2, y = 30.dp)
+ },
+ transition = {
+ translate(exiting2, x = 30.dp)
+ scaleSize(exiting3, width = 0.8f, height = 0.8f)
+ scaleDraw(entering1, scaleX = 0.5f, scaleY = 0.5f)
+ scaleSize(entering3, width = 0.2f, height = 0.2f)
+ },
+ previewProgress = 0.5f,
+ progress = 0.5f,
+ isInPreviewStage = false
+ )
+
+ // verify that exiting elements remain in the preview-end state if no further transition is
+ // defined for them in the second stage
+ val exiting1InB = layoutImpl.elements.getValue(exiting1).sceneStates.getValue(SceneB)
+ // i.e. exiting1 remains half scaled
+ assertThat(exiting1InB.lastScale).isEqualTo(Scale(0.9f, 0.9f, Offset.Unspecified))
+ // in case there is an additional transition defined for the second stage, verify that the
+ // animation is seamlessly taken over from the preview-end-state, e.g. the translation of
+ // exiting2 is at 10.dp after the preview phase. After half of the second phase, it
+ // should be half-way between 10.dp and the target-value of 30.dp -> 20.dp
+ rule.onNode(isElement(exiting2)).assertPositionInRootIsEqualTo(20.dp, 0.dp)
+ // if the element is only modified by the second phase transition, verify it's in the middle
+ // of start-scene-state and target-scene-state, i.e. exiting3 is halfway between 100.dp and
+ // 80.dp
+ rule.onNode(isElement(exiting3)).assertSizeIsEqualTo(90.dp, 90.dp)
+
+ // verify that entering elements animate seamlessly to their target state
+ val entering1InA = layoutImpl.elements.getValue(entering1).sceneStates.getValue(SceneA)
+ // e.g. entering1, which was scaled from 0f to 0.25f during the preview phase, should now be
+ // half way scaled between 0.25f and its target-state of 1f -> 0.625f
+ assertThat(entering1InA.lastScale).isEqualTo(Scale(0.625f, 0.625f, Offset.Unspecified))
+ // entering2, which was translated from y=30.dp to y=15.dp should now be half way
+ // between 15.dp and its target state of 0.dp...
+ rule.onNode(isElement(entering2)).assertPositionInRootIsEqualTo(0.dp, 7.5.dp)
+ // entering3, which isn't affected by the preview transformation should be half scaled
+ // between start size (20.dp) and target size (100.dp) -> 60.dp
+ rule.onNode(isElement(entering3)).assertSizeIsEqualTo(60.dp, 60.dp)
+ }
+
+ private fun testPreviewTransformation(
+ from: SceneKey,
+ to: SceneKey,
+ exitingElements: List<ElementKey> = listOf(),
+ enteringElements: List<ElementKey> = listOf(),
+ preview: (TransitionBuilder.() -> Unit)? = null,
+ transition: TransitionBuilder.() -> Unit,
+ progress: Float = 0f,
+ previewProgress: Float = 0.5f,
+ isInPreviewStage: Boolean = true
+ ): SceneTransitionLayoutImpl {
+ val state =
+ rule.runOnIdle {
+ MutableSceneTransitionLayoutStateImpl(
+ from,
+ transitions { from(from, to = to, preview = preview, builder = transition) }
+ )
+ }
+
+ @Composable
+ fun SceneScope.Foo(elementKey: ElementKey) {
+ Box(Modifier.element(elementKey).size(100.dp))
+ }
+
+ lateinit var layoutImpl: SceneTransitionLayoutImpl
+ rule.setContent {
+ SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+ scene(from) { Box { exitingElements.forEach { Foo(it) } } }
+ scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+ }
+ }
+
+ val bToA =
+ transition(
+ from = from,
+ to = to,
+ progress = { progress },
+ previewProgress = { previewProgress },
+ isInPreviewStage = { isInPreviewStage }
+ )
+ rule.runOnUiThread { state.startTransition(bToA) }
+ rule.waitForIdle()
+ return layoutImpl
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 6522eb3..0eaecb0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -80,6 +80,56 @@
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
assertThat(transition).hasProgress(0.4f)
+ assertThat(transition).isNotInPreviewStage()
+
+ // Cancel it.
+ rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+ assertThat(layoutState.transitionState).isIdle()
+
+ // Start again and commit it.
+ rule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(backEvent())
+ dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+ dispatcher.onBackPressed()
+ }
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
+ assertThat(layoutState.transitionState).isIdle()
+ }
+
+ @Test
+ fun testPredictiveBackWithPreview() {
+ val layoutState =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ SceneA,
+ transitions = transitions { from(SceneA, to = SceneB, preview = {}) }
+ )
+ }
+ rule.setContent {
+ SceneTransitionLayout(layoutState) {
+ scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
+
+ // Start back.
+ val dispatcher = rule.activity.onBackPressedDispatcher
+ rule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(backEvent())
+ dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
+ }
+
+ val transition = assertThat(layoutState.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ assertThat(transition).hasPreviewProgress(0.4f)
+ assertThat(transition).hasProgress(0f)
+ assertThat(transition).isInPreviewStage()
// Cancel it.
rule.runOnUiThread { dispatcher.dispatchOnBackCancelled() }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index 65f4f9e..66d4059 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -30,6 +30,9 @@
current: () -> SceneKey = { from },
progress: () -> Float = { 0f },
progressVelocity: () -> Float = { 0f },
+ previewProgress: () -> Float = { 0f },
+ previewProgressVelocity: () -> Float = { 0f },
+ isInPreviewStage: () -> Boolean = { false },
interruptionProgress: () -> Float = { 0f },
isInitiatedByUserInput: Boolean = false,
isUserInputOngoing: Boolean = false,
@@ -51,6 +54,15 @@
override val progressVelocity: Float
get() = progressVelocity()
+ override val previewProgress: Float
+ get() = previewProgress()
+
+ override val previewProgressVelocity: Float
+ get() = previewProgressVelocity()
+
+ override val isInPreviewStage: Boolean
+ get() = isInPreviewStage()
+
override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
override val isUserInputOngoing: Boolean = isUserInputOngoing
override val isUpOrLeft: Boolean = isUpOrLeft
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 825fe13..a3790f8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -168,7 +168,14 @@
@Test
fun defaultReversed() {
val transitions = transitions {
- from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ from(
+ TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } },
+ reversePreview = {
+ fractionRange(start = 0.5f, end = 0.6f) { fade(TestElements.Foo) }
+ }
+ ) {
spec = tween(500)
fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
@@ -177,11 +184,10 @@
// Fetch the transition from B to A, which will automatically reverse the transition from A
// to B we defined.
- val transformations =
- transitions
- .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
- .transformationSpec()
- .transformations
+ val transitionSpec =
+ transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
+
+ val transformations = transitionSpec.transformationSpec().transformations
assertThat(transformations)
.comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -189,6 +195,14 @@
TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
)
+
+ val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
+
+ assertThat(previewTransformations)
+ .comparingElementsUsing(TRANSFORMATION_RANGE)
+ .containsExactly(
+ TransformationRange(start = 0.5f, end = 0.6f),
+ )
}
@Test
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
index 3489892..e997a75 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt
@@ -95,6 +95,25 @@
.of(progressVelocity)
}
+ fun hasPreviewProgress(progress: Float, tolerance: Float = 0f) {
+ check("previewProgress").that(actual.previewProgress).isWithin(tolerance).of(progress)
+ }
+
+ fun hasPreviewProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) {
+ check("previewProgressVelocity")
+ .that(actual.previewProgressVelocity)
+ .isWithin(tolerance)
+ .of(progressVelocity)
+ }
+
+ fun isInPreviewStage() {
+ check("isInPreviewStage").that(actual.isInPreviewStage).isTrue()
+ }
+
+ fun isNotInPreviewStage() {
+ check("isInPreviewStage").that(actual.isInPreviewStage).isFalse()
+ }
+
fun isInitiatedByUserInput() {
check("isInitiatedByUserInput").that(actual.isInitiatedByUserInput).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
new file mode 100644
index 0000000..370adee
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.app.StatsManager
+import android.app.StatsManager.StatsPullAtomCallback
+import android.content.pm.UserInfo
+import android.platform.test.annotations.EnableFlags
+import android.util.StatsEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+@RunWith(AndroidJUnit4::class)
+class CommunalMetricsStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val metricsLogger = mock<CommunalMetricsLogger>()
+ private val statsManager = mock<StatsManager>()
+
+ private val callbackCaptor = argumentCaptor<StatsPullAtomCallback>()
+
+ private val userTracker = kosmos.fakeUserTracker
+ private val userRepository = kosmos.fakeUserRepository
+ private val widgetsRepository = kosmos.fakeCommunalWidgetRepository
+
+ private lateinit var underTest: CommunalMetricsStartable
+
+ @Before
+ fun setUp() {
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+ // Set up an existing user, which is required for widgets to show
+ val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN))
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+
+ underTest =
+ CommunalMetricsStartable(
+ kosmos.fakeExecutor,
+ kosmos.communalSettingsInteractor,
+ kosmos.communalInteractor,
+ statsManager,
+ metricsLogger,
+ )
+ }
+
+ @Test
+ fun start_communalFlagDisabled_doNotSetPullAtomCallback() {
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+
+ underTest.start()
+
+ verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
+ }
+
+ @Test
+ fun onPullAtom_atomTagDoesNotMatch_pullSkip() {
+ underTest.start()
+
+ verify(statsManager)
+ .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
+ val callback = callbackCaptor.firstValue
+
+ // Atom tag doesn't match COMMUNAL_HUB_SNAPSHOT
+ val result =
+ callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED, mutableListOf())
+
+ assertThat(result).isEqualTo(StatsManager.PULL_SKIP)
+ }
+
+ @Test
+ fun onPullAtom_atomTagMatches_pullSuccess() =
+ testScope.runTest {
+ underTest.start()
+
+ verify(statsManager)
+ .setPullAtomCallback(anyInt(), anyOrNull(), any(), callbackCaptor.capture())
+ val callback = callbackCaptor.firstValue
+
+ // Populate some widgets
+ widgetsRepository.addWidget(appWidgetId = 1, componentName = "pkg_1/cls_1")
+ widgetsRepository.addWidget(appWidgetId = 2, componentName = "pkg_2/cls_2")
+
+ val statsEvents = mutableListOf<StatsEvent>()
+ val result = callback.onPullAtom(SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT, statsEvents)
+
+ verify(metricsLogger)
+ .logWidgetsSnapshot(statsEvents, listOf("pkg_1/cls_1", "pkg_2/cls_2"))
+
+ assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 35df641..537ca03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -16,11 +16,13 @@
package com.android.systemui.communal.log
+import android.util.StatsEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.shared.system.SysUiStatsLog
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -90,4 +92,28 @@
2,
)
}
+
+ @Test
+ fun logWidgetsSnapshot_logOnlyLoggableComponents() {
+ val statsEvents = mutableListOf<StatsEvent>()
+ underTest.logWidgetsSnapshot(
+ statsEvents,
+ listOf(
+ "com.blue.package/my_test_widget_1",
+ "com.green.package/my_test_widget_2",
+ "com.red.package/my_test_widget_3",
+ "com.yellow.package/my_test_widget_4",
+ ),
+ )
+ verify(statsLogProxy)
+ .buildCommunalHubSnapshotStatsEvent(
+ componentNames =
+ arrayOf(
+ "com.blue.package/my_test_widget_1",
+ "com.red.package/my_test_widget_3",
+ ),
+ widgetCount = 4,
+ )
+ assertThat(statsEvents).hasSize(1)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 540a85a..f26c39d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -78,6 +78,7 @@
import com.android.systemui.statusbar.phone.centralSurfaces
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -274,9 +275,10 @@
}
@Test
- fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked() =
+ fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+ kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
val transitionState =
prepareState(
@@ -306,6 +308,39 @@
}
@Test
+ fun switchFromBouncerToGoneWhenDeviceUnlocked_whenDoNotLeaveOpenShade() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+ kosmos.sysuiStatusBarStateController.leaveOpen = false // don't leave shade open
+
+ val transitionState =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+
+ sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+
+ sceneInteractor.changeScene(Scenes.Bouncer, "switching to bouncer for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+
+ assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/res/drawable/ic_arrow_back_2.xml b/packages/SystemUI/res/drawable/ic_arrow_back_2.xml
new file mode 100644
index 0000000..8522d38
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_back_2.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="#000"
+ android:pathData="M640,760L200,480L640,200L640,760ZM560,480L560,480L560,480ZM560,614L560,346L350,480L560,614Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml b/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml
new file mode 100644
index 0000000..f413d900
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check_box_outline_blank.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000"
+ android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml b/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml
new file mode 100644
index 0000000..5bf914b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_radio_button_unchecked.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000"
+ android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/packages/SystemUI/res/raw/trackpad_back_edu.json b/packages/SystemUI/res/raw/trackpad_back_edu.json
new file mode 100644
index 0000000..793833d
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_back_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":900,"w":554,"h":564,"nm":"Trackpad-JSON_BackGesture","ddd":0,"assets":[{"id":"comp_0","nm":"Back_LeftDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":79},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.308,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.009,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.291,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.138,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.452,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[16.757,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.542,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.002,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.238,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[18.308,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.331,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.006,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.308,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.382,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.657,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.165,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[24.794,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.403,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.942,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.411,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.822,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.186,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.511,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[27.803,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.069,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.311,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.534,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.739,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.928,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.103,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.267,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.419,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.56,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.693,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.816,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.932,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.041,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.142,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.238,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.327,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.411,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.489,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.563,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.632,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.696,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.756,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.812,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.864,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.913,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[30.958,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.039,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.074,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.107,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.137,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.164,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.188,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.21,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.23,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.247,0,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.274,0,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.305,0,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[0.15]},"t":160,"s":[-14]},{"t":189,"s":[0]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[32,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":8}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[0,-20],[4,-24],[4,-24],[8,-20],[8,20],[4,24],[4,24],[0,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[0,-19.3],[4.7,-24],[4.7,-24],[9.401,-19.3],[9.401,19.3],[4.7,24],[4.7,24],[0,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[0,-15.017],[8.983,-24],[8.983,-24],[17.967,-15.017],[17.967,15.017],[8.983,24],[8.983,24],[0,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[0,-10.171],[13.829,-24],[13.829,-24],[27.659,-10.171],[27.659,10.171],[13.829,24],[13.829,24],[0,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[0,-7.856],[16.144,-24],[16.144,-24],[32.287,-7.856],[32.287,7.856],[16.144,24],[16.144,24],[0,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[0,-6.551],[17.449,-24],[17.449,-24],[34.898,-6.551],[34.898,6.551],[17.449,24],[17.449,24],[0,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[0,-5.766],[18.234,-24],[18.234,-24],[36.467,-5.766],[36.467,5.766],[18.234,24],[18.234,24],[0,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[0,-5.306],[18.694,-24],[18.694,-24],[37.388,-5.306],[37.388,5.306],[18.694,24],[18.694,24],[0,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[0,-5.07],[18.93,-24],[18.93,-24],[37.861,-5.07],[37.861,5.07],[18.93,24],[18.93,24],[0,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[0,-5],[19,-24],[19,-24],[38,-5],[38,5],[19,24],[19,24],[0,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[0,-1.977],[22.023,-24],[22.023,-24],[44.045,-1.977],[44.045,1.977],[22.023,24],[22.023,24],[0,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[0,-0.302],[23.698,-24],[23.698,-24],[47.396,-0.302],[47.396,0.302],[23.698,24],[23.698,24],[0,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24,-24],[48,0],[48,0],[24,24],[24,24],[0,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.149,-24],[48.149,0],[48.149,0],[24.149,24],[24,24],[0,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[24.698,-24],[48.698,0],[48.698,0],[24.698,24],[24,24],[0,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[25.714,-24],[49.714,0],[49.714,0],[25.714,24],[24,24],[0,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[26.973,-24],[50.973,0],[50.973,0],[26.973,24],[24,24],[0,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[28.19,-24],[52.19,0],[52.19,0],[28.19,24],[24,24],[0,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[29.268,-24],[53.268,0],[53.268,0],[29.268,24],[24,24],[0,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[30.206,-24],[54.206,0],[54.206,0],[30.206,24],[24,24],[0,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.028,-24],[55.028,0],[55.028,0],[31.028,24],[24,24],[0,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[31.755,-24],[55.755,0],[55.755,0],[31.755,24],[24,24],[0,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.405,-24],[56.405,0],[56.405,0],[32.405,24],[24,24],[0,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[32.99,-24],[56.99,0],[56.99,0],[32.99,24],[24,24],[0,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[33.522,-24],[57.522,0],[57.522,0],[33.522,24],[24,24],[0,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.006,-24],[58.006,0],[58.006,0],[34.006,24],[24,24],[0,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.451,-24],[58.451,0],[58.451,0],[34.451,24],[24,24],[0,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[34.861,-24],[58.861,0],[58.861,0],[34.861,24],[24,24],[0,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.24,-24],[59.24,0],[59.24,0],[35.24,24],[24,24],[0,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.591,-24],[59.591,0],[59.591,0],[35.591,24],[24,24],[0,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[35.917,-24],[59.917,0],[59.917,0],[35.917,24],[24,24],[0,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.221,-24],[60.221,0],[60.221,0],[36.221,24],[24,24],[0,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.504,-24],[60.504,0],[60.504,0],[36.504,24],[24,24],[0,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[36.769,-24],[60.769,0],[60.769,0],[36.769,24],[24,24],[0,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.017,-24],[61.017,0],[61.017,0],[37.017,24],[24,24],[0,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.248,-24],[61.248,0],[61.248,0],[37.248,24],[24,24],[0,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.465,-24],[61.465,0],[61.465,0],[37.465,24],[24,24],[0,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.669,-24],[61.669,0],[61.669,0],[37.669,24],[24,24],[0,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[37.859,-24],[61.859,0],[61.859,0],[37.859,24],[24,24],[0,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.038,-24],[62.038,0],[62.038,0],[38.038,24],[24,24],[0,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.205,-24],[62.205,0],[62.205,0],[38.205,24],[24,24],[0,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.362,-24],[62.362,0],[62.362,0],[38.362,24],[24,24],[0,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.509,-24],[62.509,0],[62.509,0],[38.509,24],[24,24],[0,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.647,-24],[62.647,0],[62.647,0],[38.647,24],[24,24],[0,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.776,-24],[62.776,0],[62.776,0],[38.776,24],[24,24],[0,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[38.896,-24],[62.896,0],[62.896,0],[38.896,24],[24,24],[0,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.008,-24],[63.008,0],[63.008,0],[39.008,24],[24,24],[0,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.113,-24],[63.113,0],[63.113,0],[39.113,24],[24,24],[0,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.21,-24],[63.21,0],[63.21,0],[39.21,24],[24,24],[0,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.3,-24],[63.3,0],[63.3,0],[39.3,24],[24,24],[0,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.384,-24],[63.384,0],[63.384,0],[39.384,24],[24,24],[0,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.461,-24],[63.461,0],[63.461,0],[39.461,24],[24,24],[0,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.532,-24],[63.532,0],[63.532,0],[39.532,24],[24,24],[0,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.597,-24],[63.597,0],[63.597,0],[39.597,24],[24,24],[0,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.657,-24],[63.657,0],[63.657,0],[39.657,24],[24,24],[0,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.711,-24],[63.711,0],[63.711,0],[39.711,24],[24,24],[0,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.76,-24],[63.76,0],[63.76,0],[39.76,24],[24,24],[0,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.804,-24],[63.804,0],[63.804,0],[39.804,24],[24,24],[0,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.843,-24],[63.843,0],[63.843,0],[39.843,24],[24,24],[0,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.877,-24],[63.877,0],[63.877,0],[39.877,24],[24,24],[0,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.907,-24],[63.907,0],[63.907,0],[39.907,24],[24,24],[0,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.932,-24],[63.932,0],[63.932,0],[39.932,24],[24,24],[0,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[0,0],[24,-24],[39.971,-24],[63.971,0],[63.971,0],[39.971,24],[24,24],[0,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277.263,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.191,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.194,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.275,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.841,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[297.7,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.568,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.993,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.914,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.504,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.464,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.661,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.021,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.499,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.068,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.705,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.401,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.143,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.925,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.741,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.585,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.345,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.074,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277.263,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.43,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.681,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.85,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.058,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.313,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.63,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.023,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.517,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.151,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.992,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.175,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.778,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.586,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[287.564,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[289.63,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[291.671,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.578,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.298,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[296.823,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.167,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.353,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[300.405,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[301.343,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.187,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[302.949,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[303.641,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.271,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.846,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.373,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[305.858,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.306,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[306.72,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.103,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.459,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[307.789,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.096,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.382,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.648,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.895,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.126,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.34,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.54,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.726,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[309.901,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.063,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.352,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.599,197.5],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.903,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.196,197.5],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[311.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[310.936,197.788],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.7,199.014],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.071,202.033],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[298.438,206.77],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[293.978,211.581],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[290.807,215.785],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.487,219.444],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.718,222.659],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[285.317,225.519],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[284.171,228.085],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.211,230.396],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.392,232.474],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.682,234.334],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[281.059,235.992],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.506,237.461],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.012,238.754],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.568,239.881],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.169,240.855],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.809,241.684],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.487,242.379],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.199,242.951],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.943,243.409],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.72,243.76],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.528,243.874],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.369,243.701],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.24,243.336],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.142,242.847],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.073,242.284],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.033,241.684],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.02,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.85,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.285,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.162,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.05,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.986,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.592,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.166,217.207],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.184,212.309],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.238,209.328],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.904,207.237],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.379,205.654],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.741,204.399],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.029,203.375],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.267,202.521],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.466,201.799],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.638,201.182],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.788,200.65],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.921,200.189],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.041,199.789],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.149,199.439],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.246,199.134],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.337,198.867],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.419,198.634],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.495,198.431],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.566,198.255],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.632,198.103],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.692,197.973],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.748,197.862],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.86,197.692],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[-28.906,14.531,0],"ti":[7.183,-8.833,0]},{"t":280,"s":[-43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[-43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[-25.86,31.8,0],"to":[7.183,-8.833,0],"ti":[-7.167,9.833,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":280,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":280,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Back_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Back_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":15},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":25,"nm":"Drop Shadow","np":8,"mn":"ADBE Drop Shadow","ix":1,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 2","np":8,"mn":"ADBE Drop Shadow","ix":2,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 3","np":8,"mn":"ADBE Drop Shadow","ix":3,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 4","np":8,"mn":"ADBE Drop Shadow","ix":4,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 5","np":8,"mn":"ADBE Drop Shadow","ix":5,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 6","np":8,"mn":"ADBE Drop Shadow","ix":6,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 7","np":8,"mn":"ADBE Drop Shadow","ix":7,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 8","np":8,"mn":"ADBE Drop Shadow","ix":8,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 9","np":8,"mn":"ADBE Drop Shadow","ix":9,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 10","np":8,"mn":"ADBE Drop Shadow","ix":10,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 11","np":8,"mn":"ADBE Drop Shadow","ix":11,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 12","np":8,"mn":"ADBE Drop Shadow","ix":12,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 13","np":8,"mn":"ADBE Drop Shadow","ix":13,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.039999999106]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":10.2}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0.394}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":1.181}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]},{"ty":25,"nm":"Drop Shadow 14","np":8,"mn":"ADBE Drop Shadow","ix":14,"en":1,"ef":[{"ty":2,"nm":"Shadow Color","mn":"ADBE Drop Shadow-0001","ix":1,"v":{"a":0,"k":[0,0,0,0.029999999329]}},{"ty":0,"nm":"Opacity","mn":"ADBE Drop Shadow-0002","ix":2,"v":{"a":0,"k":7.65}},{"ty":0,"nm":"Direction","mn":"ADBE Drop Shadow-0003","ix":3,"v":{"a":0,"k":180}},{"ty":0,"nm":"Distance","mn":"ADBE Drop Shadow-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Softness","mn":"ADBE Drop Shadow-0005","ix":5,"v":{"a":0,"k":2.362}},{"ty":7,"nm":"Shadow Only","mn":"ADBE Drop Shadow-0006","ix":6,"v":{"a":0,"k":0}}]}],"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215686275,0.125490196078,0.027450980392,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":250,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":256,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":286,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"Back_RightDismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"release Scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":0,"k":476},"y":{"a":0,"k":197}},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.08,0.08,0.08],"y":[0.47,0.47,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.999,0.999,0.999],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":254,"s":[105,105,100]},{"t":266,"s":[50,50,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onTertiary","cl":"onTertiary","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":255,"s":[100]},{"t":258,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[-0.692,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.692,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.392,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.675,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.521,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.835,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.141,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-18.925,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.386,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.622,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.692,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.714,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.39,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.692,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.766,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.041,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.549,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.178,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.787,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.326,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.795,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.206,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.57,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.894,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.187,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.453,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.695,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.917,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.122,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.312,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.487,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.65,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.802,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.944,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.076,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.2,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.316,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.424,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.526,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.621,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.711,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.795,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.873,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.947,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.015,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.08,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.14,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.196,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.248,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.297,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.342,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.384,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.422,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.458,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.49,0,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.52,0,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.547,0,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.572,0,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.594,0,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.613,0,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.645,0,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.677,0,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.36,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[5.459,5.2],[-3.459,0],[5.459,-5.2]],"c":false}]},{"i":{"x":0.02,"y":1},"o":{"x":0.167,"y":0.167},"t":152,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.779,4.88],[-3.459,0],[4.779,-4.88]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":159,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.12,"y":0},"t":162,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,7.2],[-3.459,0],[3.459,-7.2]],"c":false}]},{"t":217,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.459,9.2],[-3.459,0],[3.459,-9.2]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.121568627656,0.211764708161,0.101960785687,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Vector 1","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":257,"s":[100]},{"t":260,"s":[0]}]},"r":{"a":0,"k":0},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.06],"y":[-0.15]},"t":160,"s":[13.981]},{"t":189,"s":[-0.019]}]},"y":{"a":0,"k":0}},"a":{"a":0,"k":[-31.019,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"IndieCorners","np":21,"mn":"Pseudo/0.20784385308943532","ix":1,"en":1,"ef":[{"ty":7,"nm":"Align","mn":"Pseudo/0.20784385308943532-0001","ix":1,"v":{"a":0,"k":4}},{"ty":6,"nm":"Size","mn":"Pseudo/0.20784385308943532-0002","ix":2,"v":0},{"ty":0,"nm":"w","mn":"Pseudo/0.20784385308943532-0003","ix":3,"v":{"a":1,"k":[{"t":149,"s":[0],"h":1},{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[8]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[38]},{"i":{"x":[0.002],"y":[1]},"o":{"x":[0.119],"y":[0]},"t":162,"s":[48]},{"t":217,"s":[64]}]}},{"ty":0,"nm":"h","mn":"Pseudo/0.20784385308943532-0004","ix":4,"v":{"a":0,"k":48}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0005","ix":5,"v":0},{"ty":6,"nm":"Rounding","mn":"Pseudo/0.20784385308943532-0006","ix":6,"v":0},{"ty":7,"nm":"Same for all corners","mn":"Pseudo/0.20784385308943532-0007","ix":7,"v":{"a":0,"k":1}},{"ty":0,"nm":"All corners","mn":"Pseudo/0.20784385308943532-0008","ix":8,"v":{"a":1,"k":[{"i":{"x":[0.02],"y":[1]},"o":{"x":[0.365],"y":[0]},"t":150,"s":[80]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.336],"y":[0]},"t":159,"s":[24]},{"t":162,"s":[80]}]}},{"ty":0,"nm":"tl","mn":"Pseudo/0.20784385308943532-0009","ix":9,"v":{"a":0,"k":12}},{"ty":0,"nm":"tr","mn":"Pseudo/0.20784385308943532-0010","ix":10,"v":{"a":0,"k":12}},{"ty":0,"nm":"br","mn":"Pseudo/0.20784385308943532-0011","ix":11,"v":{"a":0,"k":12}},{"ty":0,"nm":"bl","mn":"Pseudo/0.20784385308943532-0012","ix":12,"v":{"a":0,"k":12}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0013","ix":13,"v":0},{"ty":6,"nm":"Alignment","mn":"Pseudo/0.20784385308943532-0014","ix":14,"v":0},{"ty":0,"nm":"X Anchor %","mn":"Pseudo/0.20784385308943532-0015","ix":15,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Anchor %","mn":"Pseudo/0.20784385308943532-0016","ix":16,"v":{"a":0,"k":0}},{"ty":0,"nm":"X Position","mn":"Pseudo/0.20784385308943532-0017","ix":17,"v":{"a":0,"k":0}},{"ty":0,"nm":"Y Position ","mn":"Pseudo/0.20784385308943532-0018","ix":18,"v":{"a":0,"k":0}},{"ty":6,"nm":"","mn":"Pseudo/0.20784385308943532-0019","ix":19,"v":0}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"k":[{"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[0,-24],[0,-24],[0,-24],[0,-24],[0,24],[0,24],[0,24],[0,24]],"c":true}],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.208,0],[0,0],[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208]],"o":[[0,-2.208],[0,0],[2.208,0],[0,0],[0,2.208],[0,0],[-2.208,0],[0,0]],"v":[[-8,-20],[-4,-24],[-4,-24],[0,-20],[0,20],[-4,24],[-4,24],[-8,20]],"c":true}],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-2.594,0],[0,0],[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594]],"o":[[0,-2.594],[0,0],[2.594,0],[0,0],[0,2.594],[0,0],[-2.594,0],[0,0]],"v":[[-9.401,-19.3],[-4.7,-24],[-4.7,-24],[0,-19.3],[0,19.3],[-4.7,24],[-4.7,24],[-9.401,19.3]],"c":true}],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-4.958,0],[0,0],[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958]],"o":[[0,-4.958],[0,0],[4.958,0],[0,0],[0,4.958],[0,0],[-4.958,0],[0,0]],"v":[[-17.967,-15.017],[-8.983,-24],[-8.983,-24],[0,-15.017],[0,15.017],[-8.983,24],[-8.983,24],[-17.967,15.017]],"c":true}],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-7.632,0],[0,0],[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632]],"o":[[0,-7.632],[0,0],[7.632,0],[0,0],[0,7.632],[0,0],[-7.632,0],[0,0]],"v":[[-27.659,-10.171],[-13.829,-24],[-13.829,-24],[0,-10.171],[0,10.171],[-13.829,24],[-13.829,24],[-27.659,10.171]],"c":true}],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-8.91,0],[0,0],[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91]],"o":[[0,-8.91],[0,0],[8.91,0],[0,0],[0,8.91],[0,0],[-8.91,0],[0,0]],"v":[[-32.287,-7.856],[-16.144,-24],[-16.144,-24],[0,-7.856],[0,7.856],[-16.144,24],[-16.144,24],[-32.287,7.856]],"c":true}],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-9.63,0],[0,0],[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63]],"o":[[0,-9.63],[0,0],[9.63,0],[0,0],[0,9.63],[0,0],[-9.63,0],[0,0]],"v":[[-34.898,-6.551],[-17.449,-24],[-17.449,-24],[0,-6.551],[0,6.551],[-17.449,24],[-17.449,24],[-34.898,6.551]],"c":true}],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.063,0],[0,0],[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063]],"o":[[0,-10.063],[0,0],[10.063,0],[0,0],[0,10.063],[0,0],[-10.063,0],[0,0]],"v":[[-36.467,-5.766],[-18.234,-24],[-18.234,-24],[0,-5.766],[0,5.766],[-18.234,24],[-18.234,24],[-36.467,5.766]],"c":true}],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.317,0],[0,0],[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317]],"o":[[0,-10.317],[0,0],[10.317,0],[0,0],[0,10.317],[0,0],[-10.317,0],[0,0]],"v":[[-37.388,-5.306],[-18.694,-24],[-18.694,-24],[0,-5.306],[0,5.306],[-18.694,24],[-18.694,24],[-37.388,5.306]],"c":true}],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.448,0],[0,0],[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448]],"o":[[0,-10.448],[0,0],[10.448,0],[0,0],[0,10.448],[0,0],[-10.448,0],[0,0]],"v":[[-37.861,-5.07],[-18.93,-24],[-18.93,-24],[0,-5.07],[0,5.07],[-18.93,24],[-18.93,24],[-37.861,5.07]],"c":true}],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-10.486,0],[0,0],[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486]],"o":[[0,-10.486],[0,0],[10.486,0],[0,0],[0,10.486],[0,0],[-10.486,0],[0,0]],"v":[[-38,-5],[-19,-24],[-19,-24],[0,-5],[0,5],[-19,24],[-19,24],[-38,5]],"c":true}],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-12.154,0],[0,0],[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154]],"o":[[0,-12.154],[0,0],[12.154,0],[0,0],[0,12.154],[0,0],[-12.154,0],[0,0]],"v":[[-44.045,-1.977],[-22.023,-24],[-22.023,-24],[0,-1.977],[0,1.977],[-22.023,24],[-22.023,24],[-44.045,1.977]],"c":true}],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.079,0],[0,0],[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079]],"o":[[0,-13.079],[0,0],[13.079,0],[0,0],[0,13.079],[0,0],[-13.079,0],[0,0]],"v":[[-47.396,-0.302],[-23.698,-24],[-23.698,-24],[0,-0.302],[0,0.302],[-23.698,24],[-23.698,24],[-47.396,0.302]],"c":true}],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48,0],[-24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24,24],[-48,0]],"c":true}],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.149,0],[-24.149,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.149,24],[-48.149,0]],"c":true}],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-48.698,0],[-24.698,-24],[-24,-24],[0,0],[0,0],[-24,24],[-24.698,24],[-48.698,0]],"c":true}],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-49.714,0],[-25.714,-24],[-24,-24],[0,0],[0,0],[-24,24],[-25.714,24],[-49.714,0]],"c":true}],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-50.973,0],[-26.973,-24],[-24,-24],[0,0],[0,0],[-24,24],[-26.973,24],[-50.973,0]],"c":true}],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-52.19,0],[-28.19,-24],[-24,-24],[0,0],[0,0],[-24,24],[-28.19,24],[-52.19,0]],"c":true}],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-53.268,0],[-29.268,-24],[-24,-24],[0,0],[0,0],[-24,24],[-29.268,24],[-53.268,0]],"c":true}],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-54.206,0],[-30.206,-24],[-24,-24],[0,0],[0,0],[-24,24],[-30.206,24],[-54.206,0]],"c":true}],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.028,0],[-31.028,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.028,24],[-55.028,0]],"c":true}],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-55.755,0],[-31.755,-24],[-24,-24],[0,0],[0,0],[-24,24],[-31.755,24],[-55.755,0]],"c":true}],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.405,0],[-32.405,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.405,24],[-56.405,0]],"c":true}],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-56.99,0],[-32.99,-24],[-24,-24],[0,0],[0,0],[-24,24],[-32.99,24],[-56.99,0]],"c":true}],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-57.522,0],[-33.522,-24],[-24,-24],[0,0],[0,0],[-24,24],[-33.522,24],[-57.522,0]],"c":true}],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.006,0],[-34.006,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.006,24],[-58.006,0]],"c":true}],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.451,0],[-34.451,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.451,24],[-58.451,0]],"c":true}],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-58.861,0],[-34.861,-24],[-24,-24],[0,0],[0,0],[-24,24],[-34.861,24],[-58.861,0]],"c":true}],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.24,0],[-35.24,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.24,24],[-59.24,0]],"c":true}],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.591,0],[-35.591,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.591,24],[-59.591,0]],"c":true}],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-59.917,0],[-35.917,-24],[-24,-24],[0,0],[0,0],[-24,24],[-35.917,24],[-59.917,0]],"c":true}],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.221,0],[-36.221,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.221,24],[-60.221,0]],"c":true}],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.504,0],[-36.504,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.504,24],[-60.504,0]],"c":true}],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-60.769,0],[-36.769,-24],[-24,-24],[0,0],[0,0],[-24,24],[-36.769,24],[-60.769,0]],"c":true}],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.017,0],[-37.017,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.017,24],[-61.017,0]],"c":true}],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.248,0],[-37.248,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.248,24],[-61.248,0]],"c":true}],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.465,0],[-37.465,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.465,24],[-61.465,0]],"c":true}],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.669,0],[-37.669,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.669,24],[-61.669,0]],"c":true}],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-61.859,0],[-37.859,-24],[-24,-24],[0,0],[0,0],[-24,24],[-37.859,24],[-61.859,0]],"c":true}],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.038,0],[-38.038,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.038,24],[-62.038,0]],"c":true}],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.205,0],[-38.205,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.205,24],[-62.205,0]],"c":true}],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.362,0],[-38.362,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.362,24],[-62.362,0]],"c":true}],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.509,0],[-38.509,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.509,24],[-62.509,0]],"c":true}],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.647,0],[-38.647,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.647,24],[-62.647,0]],"c":true}],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.776,0],[-38.776,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.776,24],[-62.776,0]],"c":true}],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-62.896,0],[-38.896,-24],[-24,-24],[0,0],[0,0],[-24,24],[-38.896,24],[-62.896,0]],"c":true}],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.008,0],[-39.008,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.008,24],[-63.008,0]],"c":true}],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.113,0],[-39.113,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.113,24],[-63.113,0]],"c":true}],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.21,0],[-39.21,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.21,24],[-63.21,0]],"c":true}],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.3,0],[-39.3,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.3,24],[-63.3,0]],"c":true}],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.384,0],[-39.384,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.384,24],[-63.384,0]],"c":true}],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.461,0],[-39.461,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.461,24],[-63.461,0]],"c":true}],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.532,0],[-39.532,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.532,24],[-63.532,0]],"c":true}],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.597,0],[-39.597,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.597,24],[-63.597,0]],"c":true}],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.657,0],[-39.657,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.657,24],[-63.657,0]],"c":true}],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.711,0],[-39.711,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.711,24],[-63.711,0]],"c":true}],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.76,0],[-39.76,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.76,24],[-63.76,0]],"c":true}],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.804,0],[-39.804,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.804,24],[-63.804,0]],"c":true}],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.843,0],[-39.843,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.843,24],[-63.843,0]],"c":true}],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.877,0],[-39.877,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.877,24],[-63.877,0]],"c":true}],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.907,0],[-39.907,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.907,24],[-63.907,0]],"c":true}],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.932,0],[-39.932,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.932,24],[-63.932,0]],"c":true}],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[{"i":[[0,0],[-13.246,0],[0,0],[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246]],"o":[[0,-13.246],[0,0],[13.246,0],[0,0],[0,13.246],[0,0],[-13.246,0],[0,0]],"v":[[-63.971,0],[-39.971,-24],[-24,-24],[0,0],[0,0],[-24,24],[-39.971,24],[-63.971,0]],"c":true}],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"k":[{"s":[0,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":498,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"IndieCorners Shape","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":249,"s":[100]},{"t":255,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,0,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.001,0,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.005,0,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.013,0,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.029,0,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.054,0,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.089,0,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.134,0,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.193,0,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.267,0,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.358,0,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.466,0,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.593,0,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.739,0,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.903,0,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.054,0,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.22,0,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.403,0,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.602,0,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.821,0,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.059,0,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.319,0,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.601,0,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.909,0,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.242,0,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.604,0,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.998,0,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.427,0,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.897,0,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.407,0,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.965,0,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.576,0,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.246,0,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.983,0,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.8,0,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.701,0,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.699,0,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.808,0,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.041,0,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.414,0,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.945,0,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.621,0,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.429,0,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.324,0,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.241,0,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.111,0,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-26.859,0,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.457,0,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.897,0,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-31.185,0,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.333,0,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.36,0,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.272,0,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.088,0,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-35.82,0,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.479,0,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.076,0,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-37.613,0,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.099,0,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.538,0,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.937,0,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.299,0,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.629,0,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.927,0,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.198,0,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.442,0,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.663,0,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.862,0,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.041,0,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.2,0,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.342,0,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.467,0,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.577,0,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.672,0,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.754,0,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.822,0,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.879,0,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.925,0,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.961,0,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":247,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":257,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":247,"s":[28,28]},{"t":257,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".onTertiaryFixedVariant","cl":"onTertiaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.207843139768,0.301960796118,0.184313729405,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"pb:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[276.737,197.5,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.809,197.5,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.805,197.5,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.726,197.5,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.159,197.5,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[256.3,197.5,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.431,197.5,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.009,197.5,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.087,197.5,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.496,197.5,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.536,197.5,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.339,197.5,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.98,197.5,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.502,197.5,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.933,197.5,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.295,197.5,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.599,197.5,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.857,197.5,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.075,197.5,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.259,197.5,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.415,197.5,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.655,197.5,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.926,197.5,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":256,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":7}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[276.737,197.5],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.57,197.5],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.319,197.5],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.15,197.5],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.942,197.5],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.687,197.5],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.37,197.5],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.978,197.5],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.484,197.5],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.85,197.5],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.008,197.5],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.825,197.5],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.222,197.5],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[268.416,197.5],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[266.436,197.5],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[264.37,197.5],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.33,197.5],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[260.423,197.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[258.703,197.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.178,197.5],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.833,197.5],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.646,197.5],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.594,197.5],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.657,197.5],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.814,197.5],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.052,197.5],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.36,197.5],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.73,197.5],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.154,197.5],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.627,197.5],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[248.142,197.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.694,197.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[247.28,197.5],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.897,197.5],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.541,197.5],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[246.211,197.5],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.904,197.5],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.619,197.5],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.353,197.5],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.107,197.5],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.876,197.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.661,197.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.461,197.5],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.274,197.5],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[244.099,197.5],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.937,197.5],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.787,197.5],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.648,197.5],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.52,197.5],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.291,197.5],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.098,197.5],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.866,197.5],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.655,197.5],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[242.5,197.5],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.159,197.525],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.792,197.842],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.233,199.672],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257.542,204.005],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[262.216,208.989],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[265.399,213.457],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[267.667,217.355],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[269.364,220.773],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[270.685,223.812],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.743,226.538],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.606,228.991],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.321,231.197],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[273.92,233.179],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.427,234.953],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.858,236.532],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.225,237.926],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.539,239.152],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[275.808,240.219],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.038,241.138],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.233,241.918],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.398,242.568],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.537,243.099],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.653,243.517],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.824,243.574],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.884,243.254],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.928,242.802],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.959,242.269],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.976,241.685],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,241.075],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,240.497],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.98],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.538],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.181],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,238.917],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.065],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.265],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.455],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[276.98,239.685],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.15,239.729],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.715,239.199],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.839,238.218],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.95,236.594],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[286.015,234.04],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[288.407,226.983],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.954,217.108],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.005,212.156],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.975,209.156],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[280.314,207.064],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.834,205.487],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.461,204.24],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[279.159,203.226],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.905,202.385],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.691,201.676],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.503,201.072],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.339,200.553],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.193,200.105],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[278.061,199.716],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.941,199.376],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.831,199.079],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.729,198.82],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.636,198.594],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.551,198.398],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.472,198.228],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.399,198.082],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.333,197.956],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.209,197.759],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277.063,197.577],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450982481,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"matte","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":250,"s":[0,0,0],"to":[29.688,0.625,0],"ti":[0,0,0]},{"t":280,"s":[43.1,53,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[43.1,53,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[25.86,31.8,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":255,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":273,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":287,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":307,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":250,"s":[504,315]},{"t":280,"s":[30,30],"h":1},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":250,"s":[28]},{"t":280,"s":[30],"h":1},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Back_LofiApp","parent":9,"tt":1,"tp":9,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":250,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":280,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"behindApp","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":253,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":386,"s":[0]},{"t":397,"s":[100]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"Back_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".tertiaryFixedDim","cl":"tertiaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":498,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.698039233685,0.811764717102,0.654901981354,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".onTertiaryFixed","cl":"onTertiaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[554,564]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.039215687662,0.1254902035,0.027450980619,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"container for media","bm":0,"hd":false}],"ip":0,"op":501,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Back_LeftDismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Back_RightDismiss","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":426,"op":902,"st":401,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt
new file mode 100644
index 0000000..c1cef67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalMetricsStartable.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.app.StatsManager
+import android.util.StatsEvent
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.SysUiStatsLog
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+
+@SysUISingleton
+class CommunalMetricsStartable
+@Inject
+constructor(
+ @Background private val bgExecutor: Executor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val statsManager: StatsManager,
+ private val metricsLogger: CommunalMetricsLogger,
+) : CoreStartable, StatsManager.StatsPullAtomCallback {
+ override fun start() {
+ if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
+ return
+ }
+
+ statsManager.setPullAtomCallback(
+ /* atomTag = */ SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
+ /* metadata = */ null,
+ /* executor = */ bgExecutor,
+ /* callback = */ this,
+ )
+ }
+
+ override fun onPullAtom(atomTag: Int, statsEvents: MutableList<StatsEvent>): Int {
+ if (atomTag != SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT) {
+ return StatsManager.PULL_SKIP
+ }
+
+ metricsLogger.logWidgetsSnapshot(
+ statsEvents,
+ componentNames =
+ runBlocking {
+ communalInteractor.widgetContent.first().map {
+ it.componentName.flattenToString()
+ }
+ },
+ )
+ return StatsManager.PULL_SUCCESS
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 74a2cd3..6cbf540 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -19,6 +19,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.communal.CommunalBackupRestoreStartable
import com.android.systemui.communal.CommunalDreamStartable
+import com.android.systemui.communal.CommunalMetricsStartable
import com.android.systemui.communal.CommunalOngoingContentStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
@@ -59,4 +60,9 @@
@IntoMap
@ClassKey(CommunalOngoingContentStartable::class)
fun bindCommunalOngoingContentStartable(impl: CommunalOngoingContentStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalMetricsStartable::class)
+ fun bindCommunalMetricsStartable(impl: CommunalMetricsStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 12099f7..6e345fe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.shared.log
+import android.util.StatsEvent
import com.android.systemui.communal.dagger.CommunalModule.Companion.LOGGABLE_PREFIXES
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.system.SysUiStatsLog
@@ -55,6 +56,20 @@
)
}
+ /** Logs loggable widgets and the total widget count as a [StatsEvent]. */
+ fun logWidgetsSnapshot(
+ statsEvents: MutableList<StatsEvent>,
+ componentNames: List<String>,
+ ) {
+ val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray()
+ statsEvents.add(
+ statsLogProxy.buildCommunalHubSnapshotStatsEvent(
+ componentNames = loggableComponentNames,
+ widgetCount = componentNames.size,
+ )
+ )
+ }
+
/** Whether the component name matches any of the loggable prefixes. */
private fun String.isLoggable(): Boolean {
return loggablePrefixes.any { loggablePrefix -> startsWith(loggablePrefix) }
@@ -68,6 +83,12 @@
componentName: String,
rank: Int,
)
+
+ /** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */
+ fun buildCommunalHubSnapshotStatsEvent(
+ componentNames: Array<String>,
+ widgetCount: Int,
+ ): StatsEvent
}
}
@@ -86,4 +107,15 @@
rank,
)
}
+
+ override fun buildCommunalHubSnapshotStatsEvent(
+ componentNames: Array<String>,
+ widgetCount: Int,
+ ): StatsEvent {
+ return SysUiStatsLog.buildStatsEvent(
+ SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT,
+ componentNames,
+ widgetCount,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 495e8f3..85bd0b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -71,6 +71,35 @@
stateRepository: ShortcutHelperStateRepository
) {
+ private val sources =
+ listOf(
+ InternalGroupsSource(
+ source = systemShortcutsSource,
+ isTrusted = true,
+ typeProvider = { System }
+ ),
+ InternalGroupsSource(
+ source = multitaskingShortcutsSource,
+ isTrusted = true,
+ typeProvider = { MultiTasking }
+ ),
+ InternalGroupsSource(
+ source = appCategoriesShortcutsSource,
+ isTrusted = true,
+ typeProvider = { AppCategories }
+ ),
+ InternalGroupsSource(
+ source = inputShortcutsSource,
+ isTrusted = false,
+ typeProvider = { InputMethodEditor }
+ ),
+ InternalGroupsSource(
+ source = currentAppShortcutsSource,
+ isTrusted = false,
+ typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) }
+ ),
+ )
+
private val activeInputDevice =
stateRepository.state.map {
if (it is Active) {
@@ -82,17 +111,20 @@
val categories: Flow<List<ShortcutCategory>> =
activeInputDevice
- .map {
- if (it == null) {
+ .map { inputDevice ->
+ if (inputDevice == null) {
return@map emptyList()
}
- return@map listOfNotNull(
- fetchSystemShortcuts(it),
- fetchMultiTaskingShortcuts(it),
- fetchAppCategoriesShortcuts(it),
- fetchImeShortcuts(it),
- fetchCurrentAppShortcuts(it),
- )
+ val groupsFromAllSources = sources.map { it.source.shortcutGroups(inputDevice.id) }
+ val supportedKeyCodes = fetchSupportedKeyCodes(inputDevice.id, groupsFromAllSources)
+ return@map sources.mapIndexedNotNull { index, internalGroupsSource ->
+ fetchShortcutCategory(
+ internalGroupsSource,
+ groupsFromAllSources[index],
+ inputDevice,
+ supportedKeyCodes,
+ )
+ }
}
.stateIn(
scope = backgroundScope,
@@ -100,49 +132,22 @@
initialValue = emptyList(),
)
- private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) =
- toShortcutCategory(
- inputDevice.keyCharacterMap,
- System,
- systemShortcutsSource.shortcutGroups(inputDevice.id),
- keepIcons = true,
- )
-
- private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) =
- toShortcutCategory(
- inputDevice.keyCharacterMap,
- MultiTasking,
- multitaskingShortcutsSource.shortcutGroups(inputDevice.id),
- keepIcons = true,
- )
-
- private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) =
- toShortcutCategory(
- inputDevice.keyCharacterMap,
- AppCategories,
- appCategoriesShortcutsSource.shortcutGroups(inputDevice.id),
- keepIcons = true,
- )
-
- private suspend fun fetchImeShortcuts(inputDevice: InputDevice) =
- toShortcutCategory(
- inputDevice.keyCharacterMap,
- InputMethodEditor,
- inputShortcutsSource.shortcutGroups(inputDevice.id),
- keepIcons = false,
- )
-
- private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? {
- val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id)
- val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
- return if (categoryType == null) {
+ private fun fetchShortcutCategory(
+ internalGroupsSource: InternalGroupsSource,
+ groups: List<KeyboardShortcutGroup>,
+ inputDevice: InputDevice,
+ supportedKeyCodes: Set<Int>,
+ ): ShortcutCategory? {
+ val type = internalGroupsSource.typeProvider(groups)
+ return if (type == null) {
null
} else {
toShortcutCategory(
inputDevice.keyCharacterMap,
- categoryType,
- shortcutGroups,
- keepIcons = false
+ type,
+ groups,
+ internalGroupsSource.isTrusted,
+ supportedKeyCodes,
)
}
}
@@ -162,13 +167,19 @@
type: ShortcutCategoryType,
shortcutGroups: List<KeyboardShortcutGroup>,
keepIcons: Boolean,
+ supportedKeyCodes: Set<Int>,
): ShortcutCategory? {
val subCategories =
shortcutGroups
.map { shortcutGroup ->
ShortcutSubCategory(
shortcutGroup.label.toString(),
- toShortcuts(keyCharacterMap, shortcutGroup.items, keepIcons)
+ toShortcuts(
+ keyCharacterMap,
+ shortcutGroup.items,
+ keepIcons,
+ supportedKeyCodes,
+ )
)
}
.filter { it.shortcuts.isNotEmpty() }
@@ -184,7 +195,15 @@
keyCharacterMap: KeyCharacterMap,
infoList: List<KeyboardShortcutInfo>,
keepIcons: Boolean,
- ) = infoList.mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
+ supportedKeyCodes: Set<Int>,
+ ) =
+ infoList
+ .filter {
+ // Allow KEYCODE_UNKNOWN (0) because shortcuts can have just modifiers and no
+ // keycode, or they could have a baseCharacter instead of a keycode.
+ it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
+ }
+ .mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
private fun toShortcut(
keyCharacterMap: KeyCharacterMap,
@@ -268,6 +287,29 @@
return null
}
+ private suspend fun fetchSupportedKeyCodes(
+ deviceId: Int,
+ groupsFromAllSources: List<List<KeyboardShortcutGroup>>
+ ): Set<Int> =
+ withContext(backgroundDispatcher) {
+ val allUsedKeyCodes =
+ groupsFromAllSources
+ .flatMap { groups -> groups.flatMap { group -> group.items } }
+ .map { info -> info.keycode }
+ .distinct()
+ val keyCodesSupported =
+ inputManager.deviceHasKeys(deviceId, allUsedKeyCodes.toIntArray())
+ return@withContext allUsedKeyCodes
+ .filterIndexed { index, _ -> keyCodesSupported[index] }
+ .toSet()
+ }
+
+ private class InternalGroupsSource(
+ val source: KeyboardShortcutGroupsSource,
+ val isTrusted: Boolean,
+ val typeProvider: (groups: List<KeyboardShortcutGroup>) -> ShortcutCategoryType?,
+ )
+
companion object {
private const val TAG = "SHCategoriesRepo"
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index cbe6fc7..8db16fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -98,6 +98,7 @@
import android.view.KeyEvent.KEYCODE_PAGE_DOWN
import android.view.KeyEvent.KEYCODE_PAGE_UP
import android.view.KeyEvent.KEYCODE_PERIOD
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
@@ -118,6 +119,9 @@
val keyIcons =
mapOf(
META_META_ON to R.drawable.ic_ksh_key_meta,
+ KEYCODE_BACK to R.drawable.ic_arrow_back_2,
+ KEYCODE_HOME to R.drawable.ic_radio_button_unchecked,
+ KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
)
val specialKeyLabels =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index e55e339..7c0c75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -18,14 +18,17 @@
import android.content.res.Resources
import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
import android.view.KeyEvent.KEYCODE_ENTER
import android.view.KeyEvent.KEYCODE_ESCAPE
import android.view.KeyEvent.KEYCODE_H
+import android.view.KeyEvent.KEYCODE_HOME
import android.view.KeyEvent.KEYCODE_I
import android.view.KeyEvent.KEYCODE_L
import android.view.KeyEvent.KEYCODE_N
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
@@ -60,24 +63,36 @@
command(META_META_ON)
},
// Access home screen:
+ // - Home button
// - Meta + H
// - Meta + Enter
shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
+ command(modifiers = 0, KEYCODE_HOME)
+ },
+ shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
command(META_META_ON, KEYCODE_H)
},
shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
command(META_META_ON, KEYCODE_ENTER)
},
// Overview of open apps:
+ // - Recent apps button
// - Meta + Tab
shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
+ command(modifiers = 0, KEYCODE_RECENT_APPS)
+ },
+ shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
command(META_META_ON, KEYCODE_TAB)
},
// Back: go back to previous state (back button)
+ // - Back button
// - Meta + Escape OR
// - Meta + Backspace OR
// - Meta + Left arrow
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
+ command(modifiers = 0, KEYCODE_BACK)
+ },
+ shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_ESCAPE)
},
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 4eabefc..c89ef15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -31,7 +31,12 @@
data class ShortcutCategory(
val type: ShortcutCategoryType,
val subCategories: List<ShortcutSubCategory>
-)
+) {
+ constructor(
+ type: ShortcutCategoryType,
+ vararg subCategories: ShortcutSubCategory
+ ) : this(type, subCategories.asList())
+}
class ShortcutCategoryBuilder(val type: ShortcutCategoryType) {
private val subCategories = mutableListOf<ShortcutSubCategory>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 869f00c..be64ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -91,9 +91,13 @@
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -112,6 +116,7 @@
@Composable
fun ShortcutHelper(
+ onSearchQueryChanged: (String) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
modifier: Modifier = Modifier,
shortcutsUiState: ShortcutsUiState,
@@ -119,39 +124,69 @@
) {
when (shortcutsUiState) {
is ShortcutsUiState.Active -> {
- if (useSinglePane()) {
- ShortcutHelperSinglePane(
- modifier,
- shortcutsUiState.shortcutCategories,
- shortcutsUiState.defaultSelectedCategory,
- onKeyboardSettingsClicked
- )
- } else {
- ShortcutHelperTwoPane(
- modifier,
- shortcutsUiState.shortcutCategories,
- shortcutsUiState.defaultSelectedCategory,
- onKeyboardSettingsClicked
- )
- }
+ ActiveShortcutHelper(
+ shortcutsUiState,
+ useSinglePane,
+ onSearchQueryChanged,
+ modifier,
+ onKeyboardSettingsClicked
+ )
}
- is ShortcutsUiState.Inactive -> {
+ else -> {
// No-op for now.
}
}
}
@Composable
+private fun ActiveShortcutHelper(
+ shortcutsUiState: ShortcutsUiState.Active,
+ useSinglePane: @Composable () -> Boolean,
+ onSearchQueryChanged: (String) -> Unit,
+ modifier: Modifier,
+ onKeyboardSettingsClicked: () -> Unit
+) {
+ var selectedCategoryType by
+ remember(shortcutsUiState.defaultSelectedCategory) {
+ mutableStateOf(shortcutsUiState.defaultSelectedCategory)
+ }
+ if (useSinglePane()) {
+ ShortcutHelperSinglePane(
+ shortcutsUiState.searchQuery,
+ onSearchQueryChanged,
+ shortcutsUiState.shortcutCategories,
+ selectedCategoryType,
+ onCategorySelected = { selectedCategoryType = it },
+ onKeyboardSettingsClicked,
+ modifier,
+ )
+ } else {
+ ShortcutHelperTwoPane(
+ shortcutsUiState.searchQuery,
+ onSearchQueryChanged,
+ modifier,
+ shortcutsUiState.shortcutCategories,
+ selectedCategoryType,
+ onCategorySelected = { selectedCategoryType = it },
+ onKeyboardSettingsClicked
+ )
+ }
+}
+
+@Composable
private fun shouldUseSinglePane() =
LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact ||
LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
@Composable
private fun ShortcutHelperSinglePane(
- modifier: Modifier = Modifier,
+ searchQuery: String,
+ onSearchQueryChanged: (String) -> Unit,
categories: List<ShortcutCategory>,
- defaultSelectedCategory: ShortcutCategoryType,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
+ modifier: Modifier = Modifier,
) {
Column(
modifier =
@@ -162,9 +197,9 @@
) {
TitleBar()
Spacer(modifier = Modifier.height(6.dp))
- ShortcutsSearchBar()
+ ShortcutsSearchBar(onSearchQueryChanged)
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(categories, defaultSelectedCategory)
+ CategoriesPanelSinglePane(searchQuery, categories, selectedCategoryType, onCategorySelected)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onClick = onKeyboardSettingsClicked)
}
@@ -172,16 +207,18 @@
@Composable
private fun CategoriesPanelSinglePane(
+ searchQuery: String,
categories: List<ShortcutCategory>,
- defaultSelectedCategory: ShortcutCategoryType,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
) {
- val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
- var expandedCategory by remember { mutableStateOf(selectedCategory) }
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
categories.fastForEachIndexed { index, category ->
- val isExpanded = expandedCategory == category
+ val isExpanded = selectedCategoryType == category.type
val itemShape =
- if (index == 0) {
+ if (categories.size == 1) {
+ ShortcutHelper.Shapes.singlePaneSingleCategory
+ } else if (index == 0) {
ShortcutHelper.Shapes.singlePaneFirstCategory
} else if (index == categories.lastIndex) {
ShortcutHelper.Shapes.singlePaneLastCategory
@@ -189,15 +226,17 @@
ShortcutHelper.Shapes.singlePaneCategory
}
CategoryItemSinglePane(
+ searchQuery = searchQuery,
category = category,
isExpanded = isExpanded,
onClick = {
- expandedCategory =
+ onCategorySelected(
if (isExpanded) {
null
} else {
- category
+ category.type
}
+ )
},
shape = itemShape,
)
@@ -207,6 +246,7 @@
@Composable
private fun CategoryItemSinglePane(
+ searchQuery: String,
category: ShortcutCategory,
isExpanded: Boolean,
onClick: () -> Unit,
@@ -222,13 +262,15 @@
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
) {
- ShortcutCategoryIcon(category.icon)
+ ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon)
Spacer(modifier = Modifier.width(16.dp))
Text(category.label(LocalContext.current))
Spacer(modifier = Modifier.weight(1f))
RotatingExpandCollapseIcon(isExpanded)
}
- AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) }
+ AnimatedVisibility(visible = isExpanded) {
+ ShortcutCategoryDetailsSinglePane(searchQuery, category)
+ }
}
}
}
@@ -253,8 +295,8 @@
@Composable
fun ShortcutCategoryIcon(
source: IconSource,
- contentDescription: String? = null,
modifier: Modifier = Modifier,
+ contentDescription: String? = null,
tint: Color = LocalContentColor.current
) {
if (source.imageVector != null) {
@@ -326,30 +368,30 @@
}
@Composable
-private fun ShortcutCategoryDetailsSinglePane(category: ShortcutCategory) {
+private fun ShortcutCategoryDetailsSinglePane(searchQuery: String, category: ShortcutCategory) {
Column(Modifier.padding(horizontal = 16.dp)) {
category.subCategories.fastForEach { subCategory ->
- ShortcutSubCategorySinglePane(subCategory)
+ ShortcutSubCategorySinglePane(searchQuery, subCategory)
}
}
}
@Composable
-private fun ShortcutSubCategorySinglePane(subCategory: ShortcutSubCategory) {
+private fun ShortcutSubCategorySinglePane(searchQuery: String, subCategory: ShortcutSubCategory) {
// This @Composable is expected to be in a Column.
SubCategoryTitle(subCategory.label)
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
HorizontalDivider()
}
- ShortcutSinglePane(shortcut)
+ ShortcutSinglePane(searchQuery, shortcut)
}
}
@Composable
-private fun ShortcutSinglePane(shortcut: Shortcut) {
+private fun ShortcutSinglePane(searchQuery: String, shortcut: Shortcut) {
Column(Modifier.padding(vertical = 24.dp)) {
- ShortcutDescriptionText(shortcut = shortcut)
+ ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
Spacer(modifier = Modifier.height(12.dp))
ShortcutKeyCombinations(shortcut = shortcut)
}
@@ -357,42 +399,49 @@
@Composable
private fun ShortcutHelperTwoPane(
+ searchQuery: String,
+ onSearchQueryChanged: (String) -> Unit,
modifier: Modifier = Modifier,
categories: List<ShortcutCategory>,
- defaultSelectedCategory: ShortcutCategoryType,
+ selectedCategoryType: ShortcutCategoryType?,
+ onCategorySelected: (ShortcutCategoryType?) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
) {
- var selectedCategoryType by remember { mutableStateOf(defaultSelectedCategory) }
- val selectedCategory = categories.first { it.type == selectedCategoryType }
+ val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) {
TitleBar()
Spacer(modifier = Modifier.height(12.dp))
Row(Modifier.fillMaxWidth()) {
StartSidePanel(
+ onSearchQueryChanged = onSearchQueryChanged,
modifier = Modifier.fillMaxWidth(fraction = 0.32f),
categories = categories,
- selectedCategory = selectedCategoryType,
- onCategoryClicked = { selectedCategoryType = it.type },
onKeyboardSettingsClicked = onKeyboardSettingsClicked,
+ selectedCategory = selectedCategoryType,
+ onCategoryClicked = { onCategorySelected(it.type) }
)
Spacer(modifier = Modifier.width(24.dp))
- EndSidePanel(Modifier.fillMaxSize(), selectedCategory)
+ EndSidePanel(searchQuery, Modifier.fillMaxSize(), selectedCategory)
}
}
}
@Composable
-private fun EndSidePanel(modifier: Modifier, category: ShortcutCategory) {
+private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
+ if (category == null) {
+ // TODO(b/353953351) - Show a "no results" UI?
+ return
+ }
LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
items(items = category.subCategories, key = { item -> item.label }) {
- SubCategoryContainerDualPane(it)
+ SubCategoryContainerDualPane(searchQuery, it)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
@Composable
-private fun SubCategoryContainerDualPane(subCategory: ShortcutSubCategory) {
+private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
@@ -405,7 +454,7 @@
if (index > 0) {
HorizontalDivider()
}
- ShortcutViewDualPane(shortcut)
+ ShortcutViewDualPane(searchQuery, shortcut)
}
}
}
@@ -421,7 +470,7 @@
}
@Composable
-private fun ShortcutViewDualPane(shortcut: Shortcut) {
+private fun ShortcutViewDualPane(searchQuery: String, shortcut: Shortcut) {
Row(Modifier.padding(vertical = 16.dp)) {
Row(
modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
@@ -435,6 +484,7 @@
)
}
ShortcutDescriptionText(
+ searchQuery = searchQuery,
shortcut = shortcut,
)
}
@@ -544,27 +594,53 @@
@Composable
private fun ShortcutDescriptionText(
+ searchQuery: String,
shortcut: Shortcut,
modifier: Modifier = Modifier,
) {
Text(
modifier = modifier,
- text = shortcut.label,
+ text = textWithHighlightedSearchQuery(shortcut.label, searchQuery),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
)
}
@Composable
+private fun textWithHighlightedSearchQuery(text: String, searchValue: String) =
+ buildAnnotatedString {
+ val searchIndex = text.lowercase().indexOf(searchValue.trim().lowercase())
+ val postSearchIndex = searchIndex + searchValue.trim().length
+
+ if (searchIndex > 0) {
+ val preSearchText = text.substring(0, searchIndex)
+ append(preSearchText)
+ }
+ if (searchIndex >= 0) {
+ val searchText = text.substring(searchIndex, postSearchIndex)
+ withStyle(style = SpanStyle(background = MaterialTheme.colorScheme.primaryContainer)) {
+ append(searchText)
+ }
+ if (postSearchIndex < text.length) {
+ val postSearchText = text.substring(postSearchIndex)
+ append(postSearchText)
+ }
+ } else {
+ append(text)
+ }
+ }
+
+@Composable
private fun StartSidePanel(
+ onSearchQueryChanged: (String) -> Unit,
modifier: Modifier,
categories: List<ShortcutCategory>,
onKeyboardSettingsClicked: () -> Unit,
- selectedCategory: ShortcutCategoryType,
+ selectedCategory: ShortcutCategoryType?,
onCategoryClicked: (ShortcutCategory) -> Unit,
) {
Column(modifier) {
- ShortcutsSearchBar()
+ ShortcutsSearchBar(onSearchQueryChanged)
Spacer(modifier = Modifier.heightIn(16.dp))
CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
Spacer(modifier = Modifier.weight(1f))
@@ -575,7 +651,7 @@
@Composable
private fun CategoriesPanelTwoPane(
categories: List<ShortcutCategory>,
- selectedCategory: ShortcutCategoryType,
+ selectedCategory: ShortcutCategoryType?,
onCategoryClicked: (ShortcutCategory) -> Unit
) {
Column {
@@ -643,17 +719,23 @@
@Composable
@OptIn(ExperimentalMaterial3Api::class)
-private fun ShortcutsSearchBar() {
- var query by remember { mutableStateOf("") }
+private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) {
+ // Using an "internal query" to make sure the SearchBar is immediately updated, otherwise
+ // the cursor moves to the wrong position sometimes, when waiting for the query to come back
+ // from the ViewModel.
+ var queryInternal by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
SearchBar(
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
- query = query,
+ query = queryInternal,
active = false,
onActiveChange = {},
- onQueryChange = { query = it },
+ onQueryChange = {
+ queryInternal = it
+ onQueryChange(it)
+ },
onSearch = {},
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
@@ -701,6 +783,10 @@
bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
bottomEnd = Dimensions.SinglePaneCategoryCornerRadius
)
+ val singlePaneSingleCategory =
+ RoundedCornerShape(
+ size = Dimensions.SinglePaneCategoryCornerRadius,
+ )
val singlePaneCategory = RectangleShape
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index daafc3f..d2122b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -22,8 +22,9 @@
sealed interface ShortcutsUiState {
data class Active(
+ val searchQuery: String,
val shortcutCategories: List<ShortcutCategory>,
- val defaultSelectedCategory: ShortcutCategoryType,
+ val defaultSelectedCategory: ShortcutCategoryType?,
) : ShortcutsUiState
data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index d0e3ab4..b8d0c23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -84,6 +84,7 @@
ShortcutHelper(
shortcutsUiState = shortcutsUiState,
onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+ onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 25574ea..e64cc80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,14 +19,18 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -41,6 +45,8 @@
categoriesInteractor: ShortcutHelperCategoriesInteractor,
) {
+ private val searchQuery = MutableStateFlow("")
+
val shouldShow =
categoriesInteractor.shortcutCategories
.map { it.isNotEmpty() }
@@ -48,14 +54,15 @@
.flowOn(backgroundDispatcher)
val shortcutsUiState =
- categoriesInteractor.shortcutCategories
- .map {
- if (it.isEmpty()) {
+ combine(searchQuery, categoriesInteractor.shortcutCategories) { query, categories ->
+ if (categories.isEmpty()) {
ShortcutsUiState.Inactive
} else {
+ val filteredCategories = filterCategoriesBySearchQuery(query, categories)
ShortcutsUiState.Active(
- shortcutCategories = it,
- defaultSelectedCategory = getDefaultSelectedCategory(it),
+ searchQuery = query,
+ shortcutCategories = filteredCategories,
+ defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
)
}
}
@@ -67,11 +74,47 @@
private fun getDefaultSelectedCategory(
categories: List<ShortcutCategory>
- ): ShortcutCategoryType {
+ ): ShortcutCategoryType? {
val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
- return currentAppShortcuts?.type ?: categories.first().type
+ return currentAppShortcuts?.type ?: categories.firstOrNull()?.type
}
+ private fun filterCategoriesBySearchQuery(
+ query: String,
+ categories: List<ShortcutCategory>
+ ): List<ShortcutCategory> {
+ val lowerCaseTrimmedQuery = query.trim().lowercase()
+ if (lowerCaseTrimmedQuery.isEmpty()) {
+ return categories
+ }
+ return categories
+ .map { category ->
+ category.copy(
+ subCategories =
+ filterSubCategoriesBySearchQuery(
+ subCategories = category.subCategories,
+ query = lowerCaseTrimmedQuery,
+ )
+ )
+ }
+ .filter { it.subCategories.isNotEmpty() }
+ }
+
+ private fun filterSubCategoriesBySearchQuery(
+ subCategories: List<ShortcutSubCategory>,
+ query: String
+ ) =
+ subCategories
+ .map { subCategory ->
+ subCategory.copy(
+ shortcuts = filterShortcutsBySearchQuery(subCategory.shortcuts, query)
+ )
+ }
+ .filter { it.shortcuts.isNotEmpty() }
+
+ private fun filterShortcutsBySearchQuery(shortcuts: List<Shortcut>, query: String) =
+ shortcuts.filter { shortcut -> shortcut.label.trim().lowercase().contains(query) }
+
fun onViewClosed() {
stateInteractor.onViewClosed()
}
@@ -79,4 +122,8 @@
fun onViewOpened() {
stateInteractor.onViewOpened()
}
+
+ fun onSearchQueryChanged(query: String) {
+ searchQuery.value = query
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index f4d8265..1152d70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -19,12 +19,15 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.sample
@@ -35,6 +38,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.map
@@ -51,6 +55,8 @@
transitionInteractor: KeyguardTransitionInteractor,
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
+ sceneInteractor: SceneInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -80,9 +86,26 @@
.filter { it }
.map {}
+ /**
+ * True if the any variation of the notification shade or quick settings is showing AND the
+ * device is unlocked. Else, false.
+ */
+ private val isOnShadeWhileUnlocked: Flow<Boolean> =
+ combine(
+ sceneInteractor.currentScene,
+ deviceEntryInteractor.isUnlocked,
+ ) { scene, isUnlocked ->
+ isUnlocked &&
+ (scene == Scenes.Shade ||
+ scene == Scenes.NotificationsShade ||
+ scene == Scenes.QuickSettings ||
+ scene == Scenes.QuickSettingsShade)
+ }
+ .distinctUntilChanged()
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
finishedTransitionToGone,
+ isOnShadeWhileUnlocked.filter { it }.map {},
dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction
)
.sample(dismissAction)
@@ -99,9 +122,10 @@
scene = Scenes.Bouncer,
stateWithoutSceneContainer = PRIMARY_BOUNCER
),
- transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER)
- ) { isOnGone, isOnBouncer, isOnAltBouncer ->
- !isOnGone && !isOnBouncer && !isOnAltBouncer
+ transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER),
+ isOnShadeWhileUnlocked,
+ ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked ->
+ !isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked
}
.filter { it }
.sampleFilter(dismissAction) { it !is DismissAction.None }
@@ -112,6 +136,7 @@
}
fun runAfterKeyguardGone(runnable: Runnable) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = { runnable.run() },
@@ -123,15 +148,18 @@
}
fun setDismissAction(dismissAction: DismissAction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
repository.dismissAction.value.onCancelAction.run()
repository.setDismissAction(dismissAction)
}
fun handleDismissAction() {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
repository.setDismissAction(DismissAction.None)
}
suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return
dismissInteractor.setKeyguardDone(keyguardDoneTiming)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
index b293027..74a7262 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -23,6 +23,7 @@
import com.android.systemui.log.core.LogLevel
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,7 +35,7 @@
class KeyguardDismissActionBinder
@Inject
constructor(
- private val interactor: KeyguardDismissActionInteractor,
+ private val interactorLazy: Lazy<KeyguardDismissActionInteractor>,
@Application private val scope: CoroutineScope,
private val keyguardLogger: KeyguardLogger,
) : CoreStartable {
@@ -44,6 +45,7 @@
return
}
+ val interactor = interactorLazy.get()
scope.launch {
interactor.executeDismissAction.collect {
log("executeDismissAction")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 72f37fc..3fca84e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -60,6 +60,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -130,6 +131,7 @@
private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val statusBarStateController: SysuiStatusBarStateController,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -356,8 +358,13 @@
isOnBouncer ->
// When the device becomes unlocked in Bouncer, go to previous scene,
// or Gone.
- if (previousScene.value == Scenes.Lockscreen) {
- Scenes.Gone to "device was unlocked in Bouncer scene"
+ if (
+ previousScene.value == Scenes.Lockscreen ||
+ !statusBarStateController.leaveOpenOnKeyguardHide()
+ ) {
+ Scenes.Gone to
+ "device was unlocked in Bouncer scene and shade" +
+ " didn't need to be left open"
} else {
val prevScene = previousScene.value
(prevScene ?: Scenes.Gone) to
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index fdf16aa..3fe3162 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -18,6 +18,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.Context
import android.os.UserHandle
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.ProfileType
@@ -25,7 +26,7 @@
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -38,6 +39,7 @@
@Inject
constructor(
private val profileTypes: ProfileTypeRepository,
+ private val context: Context,
) : CapturePolicy {
override suspend fun check(content: DisplayContentModel): PolicyResult {
@@ -46,7 +48,7 @@
return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
- if (Flags.enableDesktopWindowingMode()) {
+ if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
content.rootTasks.firstOrNull()?.also {
if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 85f4c36..ecb6d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,12 +17,17 @@
package com.android.systemui.statusbar.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@@ -45,4 +50,13 @@
@IntoMap
@ClassKey(LightBarController::class)
abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @OngoingCallLog
+ fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("OngoingCall", 75)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 1cbb16e..06af980 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -68,6 +68,7 @@
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ContrastColorUtil;
@@ -1873,7 +1874,8 @@
SmartReplyConstants smartReplyConstants,
SmartReplyController smartReplyController,
FeatureFlags featureFlags,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ UiEventLogger uiEventLogger) {
mEntry = entry;
mAppName = appName;
if (mMenuRow == null) {
@@ -1901,7 +1903,9 @@
rivSubcomponentFactory,
smartReplyConstants,
smartReplyController,
- statusBarService);
+ statusBarService,
+ uiEventLogger
+ );
}
mOnUserInteractionCallback = onUserInteractionCallback;
mBubblesManagerOptional = bubblesManagerOptional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index e59829b..4c76e328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,6 +33,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -112,6 +113,7 @@
private final ExpandableNotificationRowDragController mDragController;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final IStatusBarService mStatusBarService;
+ private final UiEventLogger mUiEventLogger;
private final NotificationSettingsController mSettingsController;
@@ -230,7 +232,8 @@
NotificationSettingsController settingsController,
ExpandableNotificationRowDragController dragController,
NotificationDismissibilityProvider dismissibilityProvider,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ UiEventLogger uiEventLogger) {
mView = view;
mListContainer = listContainer;
mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -265,6 +268,7 @@
mSmartReplyController = smartReplyController;
mDismissibilityProvider = dismissibilityProvider;
mStatusBarService = statusBarService;
+ mUiEventLogger = uiEventLogger;
}
/**
@@ -298,7 +302,8 @@
mSmartReplyConstants,
mSmartReplyController,
mFeatureFlags,
- mStatusBarService
+ mStatusBarService,
+ mUiEventLogger
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt
new file mode 100644
index 0000000..ab5731c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationCompactHeadsUpEvent.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class NotificationCompactHeadsUpEvent(val eventId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Minimal HUN is shown to the user") NOTIFICATION_COMPACT_HUN_SHOWN(1857);
+
+ override fun getId(): Int = eventId
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 646d0b1..3f46902 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Flags;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -45,6 +46,7 @@
import androidx.annotation.MainThread;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.res.R;
@@ -58,6 +60,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactHeadsUpTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -205,6 +208,7 @@
private int mUnrestrictedContentHeight;
private boolean mContentAnimating;
+ private UiEventLogger mUiEventLogger;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -217,12 +221,14 @@
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
SmartReplyConstants smartReplyConstants,
SmartReplyController smartReplyController,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ UiEventLogger uiEventLogger) {
mPeopleIdentifier = peopleNotificationIdentifier;
mRemoteInputSubcomponentFactory = rivSubcomponentFactory;
mSmartReplyConstants = smartReplyConstants;
mSmartReplyController = smartReplyController;
mStatusBarService = statusBarService;
+ mUiEventLogger = uiEventLogger;
// We set root namespace so that we avoid searching children for id. Notification might
// contain custom view and their ids may clash with ids already existing in shade or
// notification panel
@@ -552,6 +558,12 @@
mHeadsUpChild = child;
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+
+ if (Flags.compactHeadsUpNotification()
+ && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper) {
+ logCompactHUNShownEvent();
+ }
+
if (mContainingNotification != null) {
applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
}
@@ -559,6 +571,15 @@
updateShownWrapper(mVisibleType);
}
+ private void logCompactHUNShownEvent() {
+ if (mUiEventLogger == null) {
+ return;
+ }
+
+ mUiEventLogger.log(
+ NotificationCompactHeadsUpEvent.NOTIFICATION_COMPACT_HUN_SHOWN);
+ }
+
/**
* Sets the single-line view. Child may be null to remove the view.
* @param child single-line content view to set
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index b801e5d..aee1d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static androidx.core.math.MathUtils.clamp;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -899,11 +901,33 @@
if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
childState.headsUpIsVisible, row.showingPulsing(),
ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+ // the height of this child before clamping it to the top
+ float unmodifiedChildHeight = childState.height;
clampHunToTop(
/* headsUpTop = */ headsUpTranslation,
/* collapsedHeight = */ row.getCollapsedHeight(),
/* viewState = */ childState
);
+ float baseZ = ambientState.getBaseZHeight();
+ if (headsUpTranslation < ambientState.getStackTop()) {
+ // HUN displayed above the stack top, it needs a fix shadow
+ childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
+ } else {
+ // HUN displayed within the stack, add a shadow if it overlaps with
+ // other elements.
+ //
+ // Views stack vertically from the top. Add the HUN's original height
+ // (before clamping) to the stack top, to determine the starting
+ // point for the remaining content.
+ float scrollingContentTop =
+ ambientState.getStackTop() + unmodifiedChildHeight;
+ updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ scrollingContentTop,
+ /* scrollingContentTopPadding = */ mGapHeight,
+ /* baseZ = */ baseZ,
+ /* viewState = */ childState
+ );
+ }
if (isTopEntry && row.isAboveShelf()) {
clampHunToMaxTranslation(
/* headsUpTop = */ headsUpTranslation,
@@ -1040,8 +1064,30 @@
// Transition from collapsed pinned state to fully expanded state
// when the pinned HUN approaches its actual location (when scrolling back to top).
final float distToRealY = newTranslation - viewState.getYTranslation();
- viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+ final float availableHeight = viewState.height - distToRealY;
+
viewState.setYTranslation(newTranslation);
+ viewState.height = (int) Math.max(availableHeight, collapsedHeight);
+ }
+
+ @VisibleForTesting
+ void updateZTranslationForHunInStack(float scrollingContentTop,
+ float scrollingContentTopPadding, float baseZ, ExpandableViewState viewState) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ float hunBottom = viewState.getYTranslation() + viewState.height;
+ float overlap = Math.max(0f, hunBottom - scrollingContentTop);
+
+ float shadowFraction = 1f;
+ if (scrollingContentTopPadding > 0f) {
+ // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
+ // of the scrolling content. Use this to animate to the full shadow.
+ shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+ }
+
+ if (overlap > 0.0f) {
+ // add a shadow to this HUN, because it overlaps with the scrolling stack
+ viewState.setZTranslation(baseZ + shadowFraction * mPinnedZTranslationExtra);
+ }
}
// Pin HUN to bottom of expanded QS
@@ -1066,7 +1112,7 @@
private void clampHunToMaxTranslation(float headsUpTop, float headsUpBottom,
ExpandableViewState viewState) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- final float maxHeight = headsUpTop - headsUpBottom;
+ final float maxHeight = Math.max(0f, headsUpBottom - headsUpTop);
viewState.setYTranslation(Math.min(headsUpTop, viewState.getYTranslation()));
viewState.height = (int) Math.min(maxHeight, viewState.height);
}
@@ -1151,53 +1197,65 @@
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
- if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
- && !ambientState.isDozingAndNotPulsing(child)
- && childViewState.getYTranslation() < ambientState.getTopPadding()
- + ambientState.getStackTranslation()) {
-
- if (childrenOnTop != 0.0f) {
- // To elevate the later HUN over previous HUN when multiple HUNs exist
- childrenOnTop++;
- } else {
- // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
- // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
- // When scrolling down shade to make HUN back to in-position in Notification Panel,
- // The overlapping fraction goes to 0, and shadows hides gradually.
- float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.getYTranslation();
- // To prevent over-shadow during HUN entry
- childrenOnTop += Math.min(
- 1.0f,
- overlap / childViewState.height
- );
- }
- childViewState.setZTranslation(baseZ
- + childrenOnTop * mPinnedZTranslationExtra);
- } else if (isTopHun) {
- // In case this is a new view that has never been measured before, we don't want to
- // elevate if we are currently expanded more than the notification
- int shelfHeight = ambientState.getShelf() == null ? 0 :
- ambientState.getShelf().getIntrinsicHeight();
- float shelfStart = ambientState.getInnerHeight()
- - shelfHeight + ambientState.getTopPadding()
- + ambientState.getStackTranslation();
- float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
- + mPaddingBetweenElements;
- if (shelfStart > notificationEnd) {
- // When the notification doesn't overlap with Notification Shelf, there's no shadow
- childViewState.setZTranslation(baseZ);
- } else {
- // Give shadow to the notification if it overlaps with Notification Shelf
- float factor = (notificationEnd - shelfStart) / shelfHeight;
- if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
- factor = 1.0f;
- }
- factor = Math.min(factor, 1.0f);
- childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
- }
- } else {
+ if (SceneContainerFlag.isEnabled()) {
+ // SceneContainer flags off this logic, and just sets the baseZ because:
+ // - there are no overlapping HUNs anymore, no need for multiplying their shadows
+ // - shadows for HUNs overlapping with the stack are now set from updateHeadsUpStates
+ // - shadows for HUNs overlapping with the shelf are NOT set anymore, because it only
+ // happens on AOD/Pulsing, where they're displayed on a black background so a shadow
+ // wouldn't be visible.
childViewState.setZTranslation(baseZ);
+ } else {
+ if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
+ && !ambientState.isDozingAndNotPulsing(child)
+ && childViewState.getYTranslation() < ambientState.getTopPadding()
+ + ambientState.getStackTranslation()) {
+
+ if (childrenOnTop != 0.0f) {
+ // To elevate the later HUN over previous HUN when multiple HUNs exist
+ childrenOnTop++;
+ } else {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notif Panel,
+ // The overlapping fraction goes to 0, and shadows hides gradually.
+ float overlap = ambientState.getTopPadding()
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
+ // To prevent over-shadow during HUN entry
+ childrenOnTop += Math.min(
+ 1.0f,
+ overlap / childViewState.height
+ );
+ }
+ childViewState.setZTranslation(baseZ
+ + childrenOnTop * mPinnedZTranslationExtra);
+ } else if (isTopHun) {
+ // In case this is a new view that has never been measured before, we don't want to
+ // elevate if we are currently expanded more than the notification
+ int shelfHeight = ambientState.getShelf() == null ? 0 :
+ ambientState.getShelf().getIntrinsicHeight();
+ float shelfStart = ambientState.getInnerHeight()
+ - shelfHeight + ambientState.getTopPadding()
+ + ambientState.getStackTranslation();
+ float notificationEnd =
+ childViewState.getYTranslation() + child.getIntrinsicHeight()
+ + mPaddingBetweenElements;
+ if (shelfStart > notificationEnd) {
+ // When the notification doesn't overlap with Notification Shelf,
+ // there's no shadow
+ childViewState.setZTranslation(baseZ);
+ } else {
+ // Give shadow to the notification if it overlaps with Notification Shelf
+ float factor = (notificationEnd - shelfStart) / shelfHeight;
+ if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
+ factor = 1.0f;
+ }
+ factor = Math.min(factor, 1.0f);
+ childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
+ }
+ } else {
+ childViewState.setZTranslation(baseZ);
+ }
}
// While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index e01556f..c046168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -341,6 +341,7 @@
mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
.commitUpdate(mContext.getDisplayId());
+ mDelegate.onStart(this);
start();
}
@@ -349,7 +350,8 @@
* should override this method instead.
*/
protected void start() {
- mDelegate.onStart(this);
+ // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
+ // Instead, add things to onStop above.
}
@Override
@@ -365,6 +367,7 @@
mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false)
.commitUpdate(mContext.getDisplayId());
+ mDelegate.onStop(this);
stop();
}
@@ -373,7 +376,8 @@
* should override this method instead.
*/
protected void stop() {
- mDelegate.onStop(this);
+ // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
+ // Instead, add things to onStop above.
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0320a7a..07c190d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -661,9 +661,6 @@
} else {
hideOngoingActivityChip(animate);
}
- if (!Flags.statusBarScreenSharingChips()) {
- mOngoingCallController.notifyChipVisibilityChanged(showOngoingActivityChip);
- }
}
private boolean shouldHideStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 9f98b54..3898088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -23,7 +23,6 @@
import android.app.PendingIntent
import android.app.UidObserver
import android.content.Context
-import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
@@ -35,6 +34,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -68,11 +69,11 @@
private val activityStarter: ActivityStarter,
@Main private val mainExecutor: Executor,
private val iActivityManager: IActivityManager,
- private val logger: OngoingCallLogger,
private val dumpManager: DumpManager,
private val statusBarWindowController: StatusBarWindowController,
private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
private val statusBarModeRepository: StatusBarModeRepositoryStore,
+ @OngoingCallLog private val logger: LogBuffer,
) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable {
private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
@@ -122,8 +123,20 @@
callNotificationInfo = newOngoingCallInfo
if (newOngoingCallInfo.isOngoing) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = newOngoingCallInfo.key },
+ { "Call notif *is* ongoing -> showing chip. key=$str1" },
+ )
updateChip()
} else {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = newOngoingCallInfo.key },
+ { "Call notif not ongoing -> hiding chip. key=$str1" },
+ )
removeChip()
}
}
@@ -131,6 +144,12 @@
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
if (entry.sbn.key == callNotificationInfo?.key) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = entry.sbn.key },
+ { "Call notif removed -> hiding chip. key=$str1" },
+ )
removeChip()
}
}
@@ -151,7 +170,8 @@
/**
* Sets the chip view that will contain ongoing call information.
*
- * Should only be called from [CollapsedStatusBarFragment].
+ * Should only be called from
+ * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment].
*/
fun setChipView(chipView: View) {
tearDownChipView()
@@ -165,15 +185,6 @@
}
/**
- * Called when the chip's visibility may have changed.
- *
- * Should only be called from [CollapsedStatusBarFragment].
- */
- fun notifyChipVisibilityChanged(chipIsVisible: Boolean) {
- logger.logChipVisibilityChanged(chipIsVisible)
- }
-
- /**
* Returns true if there's an active ongoing call that should be displayed in a status bar chip.
*/
fun hasOngoingCall(): Boolean {
@@ -250,14 +261,12 @@
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
// will return false and we fall back to typical notification handling.
callNotificationInfo = null
-
- if (DEBUG) {
- Log.w(
- TAG,
- "Ongoing call chip view could not be found; " +
- "Not displaying chip in status bar"
- )
- }
+ logger.log(
+ TAG,
+ LogLevel.WARNING,
+ {},
+ { "Ongoing call chip view could not be found; Not displaying chip in status bar" },
+ )
}
}
@@ -275,7 +284,6 @@
val intent = callNotificationInfo?.intent
if (currentChipView != null && backgroundView != null && intent != null) {
currentChipView.setOnClickListener {
- logger.logChipClicked()
activityStarter.postStartActivityDismissingKeyguard(
intent,
ActivityTransitionAnimator.Controller.fromView(
@@ -333,9 +341,7 @@
* detected.
*/
private fun onSwipeAwayGestureDetected() {
- if (DEBUG) {
- Log.d(TAG, "Swipe away gesture detected")
- }
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" })
callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
@@ -368,7 +374,10 @@
pw.println("Call app visible: ${uidObserver.isCallAppVisible}")
}
- /** Our implementation of a [IUidObserver]. */
+ /**
+ * Observer to tell us when the app that posted the ongoing call notification is visible so that
+ * we don't show the call chip at the same time (since the timers could be out-of-sync).
+ */
inner class CallAppUidObserver : UidObserver() {
/** True if the application managing the call is visible to the user. */
var isCallAppVisible: Boolean = false
@@ -395,6 +404,12 @@
isProcessVisibleToUser(
iActivityManager.getUidProcessState(uid, context.opPackageName)
)
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCallAppVisible },
+ { "On uid observer registration, isCallAppVisible=$bool1" },
+ )
if (isRegistered) {
return
}
@@ -406,7 +421,13 @@
)
isRegistered = true
} catch (se: SecurityException) {
- Log.e(TAG, "Security exception when trying to set up uid observer: $se")
+ logger.log(
+ TAG,
+ LogLevel.ERROR,
+ {},
+ { "Security exception when trying to set up uid observer" },
+ se,
+ )
}
}
@@ -431,6 +452,12 @@
val oldIsCallAppVisible = isCallAppVisible
isCallAppVisible = isProcessVisibleToUser(procState)
if (oldIsCallAppVisible != isCallAppVisible) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCallAppVisible },
+ { "#onUidStateChanged. isCallAppVisible=$bool1" },
+ )
// Animations may be run as a result of the call's state change, so ensure
// the listener is notified on the main thread.
mainExecutor.execute { sendStateChangeEvent() }
@@ -443,5 +470,4 @@
return entry.sbn.notification.isStyle(Notification.CallStyle::class.java)
}
-private const val TAG = "OngoingCallController"
-private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+private const val TAG = OngoingCallRepository.TAG
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt
new file mode 100644
index 0000000..5f53f87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLog.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for events related to ongoing call notifications and their corresponding status bar chip.
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class OngoingCallLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
deleted file mode 100644
index 177f215..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.ongoingcall
-
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-
-/** A class to log events for the ongoing call chip. */
-@SysUISingleton
-class OngoingCallLogger @Inject constructor(private val logger: UiEventLogger) {
-
- private var chipIsVisible: Boolean = false
-
- /** Logs that the ongoing call chip was clicked. */
- fun logChipClicked() {
- logger.log(OngoingCallEvents.ONGOING_CALL_CLICKED)
- }
-
- /**
- * If needed, logs that the ongoing call chip's visibility has changed.
- *
- * For now, only logs when the chip changes from not visible to visible.
- */
- fun logChipVisibilityChanged(chipIsVisible: Boolean) {
- if (chipIsVisible && chipIsVisible != this.chipIsVisible) {
- logger.log(OngoingCallEvents.ONGOING_CALL_VISIBLE)
- }
- this.chipIsVisible = chipIsVisible
- }
-
- @VisibleForTesting
- enum class OngoingCallEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The ongoing call chip became visible")
- ONGOING_CALL_VISIBLE(813),
-
- @UiEvent(doc = "The ongoing call chip was clicked")
- ONGOING_CALL_CLICKED(814);
-
- override fun getId() = metricId
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9317ebe5..f16371a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.phone.ongoingcall.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,7 +35,11 @@
* classes both refer to this repository.
*/
@SysUISingleton
-class OngoingCallRepository @Inject constructor() {
+class OngoingCallRepository
+@Inject
+constructor(
+ @OngoingCallLog private val logger: LogBuffer,
+) {
private val _ongoingCallState = MutableStateFlow<OngoingCallModel>(OngoingCallModel.NoCall)
/** The current ongoing call state. */
val ongoingCallState: StateFlow<OngoingCallModel> = _ongoingCallState.asStateFlow()
@@ -42,6 +49,16 @@
* [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
*/
fun setOngoingCallState(state: OngoingCallModel) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = state::class.simpleName },
+ { "Repo#setOngoingCallState: $str1" },
+ )
_ongoingCallState.value = state
}
+
+ companion object {
+ const val TAG = "OngoingCall"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index f81b952..2c48487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -18,13 +18,7 @@
import android.app.PendingIntent
-/**
- * Represents the state of any ongoing calls.
- *
- * TODO(b/332662551): If there's an ongoing call but the user has the call app open, then we use the
- * NoCall model, *not* the InCall model, which is confusing when looking at the logs. We may want
- * to make that more clear, either with better logging or different models.
- */
+/** Represents the state of any ongoing calls. */
sealed interface OngoingCallModel {
/** There is no ongoing call. */
data object NoCall : OngoingCallModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 7b82b56..a115baa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -185,7 +185,6 @@
@Override
public void stopCasting(CastDevice device) {
- // TODO(b/332662551): Convert Logcat to LogBuffer.
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
mLogger.logStopCasting(isProjection);
if (isProjection) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 396c6b8..8721b69 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -16,9 +16,11 @@
package com.android.systemui.touchpad.tutorial.ui.composable
+import android.graphics.ColorFilter
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
-import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -37,31 +39,46 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.LottieDynamicProperties
+import com.airbnb.lottie.compose.LottieDynamicProperty
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
-@OptIn(ExperimentalComposeUiApi::class)
+data class TutorialScreenColors(
+ val backgroundColor: Color,
+ val titleColor: Color,
+ val animationProperties: LottieDynamicProperties
+)
+
@Composable
fun BackGestureTutorialScreen(
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
+ val screenColors = rememberScreenColors()
BackHandler(onBack = onBack)
var gestureDone by remember { mutableStateOf(false) }
val swipeDistanceThresholdPx =
- with(LocalContext.current) {
- resources.getDimensionPixelSize(
- com.android.internal.R.dimen.system_gestures_distance_threshold
- )
- }
+ LocalContext.current.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_distance_threshold
+ )
val gestureHandler =
remember(swipeDistanceThresholdPx) {
TouchpadGestureHandler(BACK, swipeDistanceThresholdPx, onDone = { gestureDone = true })
@@ -73,17 +90,45 @@
// only available in MotionEvent
.pointerInteropFilter(onTouchEvent = gestureHandler::onMotionEvent)
) {
- GestureTutorialContent(gestureDone, onDoneButtonClicked)
+ GestureTutorialContent(gestureDone, onDoneButtonClicked, screenColors)
}
}
@Composable
-private fun GestureTutorialContent(gestureDone: Boolean, onDoneButtonClicked: () -> Unit) {
+private fun rememberScreenColors(): TutorialScreenColors {
+ val onTertiary = LocalAndroidColorScheme.current.onTertiary
+ val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
+ val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
+ val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
+ val dynamicProperties =
+ rememberLottieDynamicProperties(
+ rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
+ rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
+ rememberColorFilterProperty(".onTertiary", onTertiary),
+ rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
+ )
+ val screenColors =
+ remember(onTertiaryFixed, tertiaryFixedDim, dynamicProperties) {
+ TutorialScreenColors(
+ backgroundColor = onTertiaryFixed,
+ titleColor = tertiaryFixedDim,
+ animationProperties = dynamicProperties,
+ )
+ }
+ return screenColors
+}
+
+@Composable
+private fun GestureTutorialContent(
+ gestureDone: Boolean,
+ onDoneButtonClicked: () -> Unit,
+ screenColors: TutorialScreenColors
+) {
Column(
verticalArrangement = Arrangement.Center,
modifier =
Modifier.fillMaxSize()
- .background(color = MaterialTheme.colorScheme.surfaceContainer)
+ .background(color = screenColors.backgroundColor)
.padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
) {
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
@@ -91,11 +136,15 @@
titleTextId =
if (gestureDone) R.string.touchpad_tutorial_gesture_done
else R.string.touchpad_back_gesture_action_title,
+ titleColor = screenColors.titleColor,
bodyTextId = R.string.touchpad_back_gesture_guidance,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(76.dp))
- TutorialAnimation(modifier = Modifier.weight(1f).padding(top = 24.dp))
+ TutorialAnimation(
+ screenColors.animationProperties,
+ modifier = Modifier.weight(1f).padding(top = 8.dp)
+ )
}
DoneButton(onDoneButtonClicked = onDoneButtonClicked)
}
@@ -104,34 +153,53 @@
@Composable
fun TutorialDescription(
@StringRes titleTextId: Int,
+ titleColor: Color,
@StringRes bodyTextId: Int,
modifier: Modifier = Modifier
) {
Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
- Text(text = stringResource(id = titleTextId), style = MaterialTheme.typography.displayLarge)
+ Text(
+ text = stringResource(id = titleTextId),
+ style = MaterialTheme.typography.displayLarge,
+ color = titleColor
+ )
Spacer(modifier = Modifier.height(16.dp))
- Text(text = stringResource(id = bodyTextId), style = MaterialTheme.typography.bodyLarge)
+ Text(
+ text = stringResource(id = bodyTextId),
+ style = MaterialTheme.typography.bodyLarge,
+ color = Color.White
+ )
}
}
@Composable
-fun TutorialAnimation(modifier: Modifier = Modifier) {
- // below are just placeholder images, will be substituted by animations soon
+fun TutorialAnimation(animationProperties: LottieDynamicProperties, modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxWidth()) {
- Image(
- painter = painterResource(id = R.drawable.placeholder_touchpad_tablet_back_gesture),
- contentDescription =
- stringResource(
- id = R.string.touchpad_back_gesture_screen_animation_content_description
- ),
- modifier = Modifier.fillMaxWidth()
- )
- Spacer(modifier = Modifier.height(24.dp))
- Image(
- painter = painterResource(id = R.drawable.placeholder_touchpad_back_gesture),
- contentDescription =
- stringResource(id = R.string.touchpad_back_gesture_animation_content_description),
- modifier = Modifier.fillMaxWidth()
+ val composition by
+ rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.trackpad_back_edu))
+ val progress by
+ animateLottieCompositionAsState(
+ composition,
+ iterations = LottieConstants.IterateForever
+ )
+ LottieAnimation(
+ composition = composition,
+ progress = { progress },
+ dynamicProperties = animationProperties
)
}
}
+
+@Composable
+fun rememberColorFilterProperty(
+ layerName: String,
+ color: Color
+): LottieDynamicProperty<ColorFilter> {
+ return rememberLottieDynamicProperty(
+ LottieProperty.COLOR_FILTER,
+ value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
+ // "**" below means match zero or more layers, so ** layerName ** means find layer with that
+ // name at any depth
+ keyPath = arrayOf("**", layerName, "**")
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index e01366a..6ff1b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -27,14 +27,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.AdditionalAnswers.returnsSecondArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +47,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.util.Arrays.asList;
+
import android.animation.ValueAnimator;
import android.annotation.IdRes;
import android.annotation.Nullable;
@@ -72,7 +68,6 @@
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableResources;
-import android.text.TextUtils;
import android.util.Size;
import android.view.AttachedSurfaceControl;
import android.view.Display;
@@ -274,7 +269,7 @@
verify(mSecureSettings).getIntForUser(
eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
/* def */ eq(1), /* userHandle= */ anyInt());
- assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled());
+ assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
}
@Test
@@ -340,10 +335,10 @@
final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
(eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertEquals(mWindowMagnificationController.getCenterX(),
- sourceBoundsCaptor.getValue().exactCenterX(), 0);
- assertEquals(mWindowMagnificationController.getCenterY(),
- sourceBoundsCaptor.getValue().exactCenterY(), 0);
+ assertThat(mWindowMagnificationController.getCenterX())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+ assertThat(mWindowMagnificationController.getCenterY())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
}
@Test
@@ -356,7 +351,7 @@
waitForIdleSync();
List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
- assertFalse(rects.isEmpty());
+ assertThat(rects).isNotEmpty();
}
@Ignore("The default window size should be constrained after fixing b/288056772")
@@ -374,8 +369,8 @@
ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
// The frame size should be the half of smaller value of window height/width unless it
//exceed the max frame size.
- assertTrue(params.width < halfScreenSize);
- assertTrue(params.height < halfScreenSize);
+ assertThat(params.width).isLessThan(halfScreenSize);
+ assertThat(params.height).isLessThan(halfScreenSize);
}
@Test
@@ -409,7 +404,7 @@
});
verify(mMirrorWindowControl).destroyControl();
- assertFalse(hasMagnificationOverlapFlag());
+ assertThat(hasMagnificationOverlapFlag()).isFalse();
}
@Test
@@ -468,12 +463,12 @@
verify(mAnimationCallback, never()).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertEquals(mWindowMagnificationController.getCenterX(),
- sourceBoundsCaptor.getValue().exactCenterX(), 0);
- assertEquals(mWindowMagnificationController.getCenterY(),
- sourceBoundsCaptor.getValue().exactCenterY(), 0);
- assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
- assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+ assertThat(mWindowMagnificationController.getCenterX())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+ assertThat(mWindowMagnificationController.getCenterY())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+ assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
+ assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
}
@Test
@@ -509,12 +504,12 @@
verify(mAnimationCallback, times(3)).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertEquals(mWindowMagnificationController.getCenterX(),
- sourceBoundsCaptor.getValue().exactCenterX(), 0);
- assertEquals(mWindowMagnificationController.getCenterY(),
- sourceBoundsCaptor.getValue().exactCenterY(), 0);
- assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
- assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+ assertThat(mWindowMagnificationController.getCenterX())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+ assertThat(mWindowMagnificationController.getCenterY())
+ .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+ assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
+ assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
}
@Test
@@ -525,10 +520,10 @@
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
- assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+ assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
final View mirrorView = mSurfaceControlViewHost.getView();
- assertNotNull(mirrorView);
- assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
+ assertThat(mirrorView).isNotNull();
+ assertThat(mirrorView.getStateDescription().toString()).contains("300");
}
@Test
@@ -567,12 +562,12 @@
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
ActivityInfo.CONFIG_ORIENTATION));
- assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
final PointF expectedCenter = new PointF(magnifiedCenter.y,
displayWidth - magnifiedCenter.x);
final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
mWindowMagnificationController.getCenterY());
- assertEquals(expectedCenter, actualCenter);
+ assertThat(actualCenter).isEqualTo(expectedCenter);
}
@Test
@@ -587,7 +582,7 @@
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
ActivityInfo.CONFIG_ORIENTATION));
- assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
}
@Test
@@ -614,12 +609,10 @@
});
// The ratio of center to window size should be the same.
- assertEquals(expectedRatio,
- mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
- 0);
- assertEquals(expectedRatio,
- mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
- 0);
+ assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
+ .isEqualTo(expectedRatio);
+ assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
+ .isEqualTo(expectedRatio);
}
@DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@@ -657,8 +650,8 @@
final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
R.dimen.magnification_mirror_surface_margin);
// The width and height of the view include the magnification frame and the margins.
- assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
- assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+ assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+ assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
}
@EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@@ -701,8 +694,8 @@
final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
R.dimen.magnification_mirror_surface_margin);
// The width and height of the view include the magnification frame and the margins.
- assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
- assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+ assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+ assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
}
@Test
@@ -726,8 +719,8 @@
WindowMagnificationSettings.MagnificationSize.MEDIUM);
ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
- assertTrue(params.width == defaultWindowSize);
- assertTrue(params.height == defaultWindowSize);
+ assertThat(params.width).isEqualTo(defaultWindowSize);
+ assertThat(params.height).isEqualTo(defaultWindowSize);
}
@Test
@@ -766,20 +759,29 @@
Float.NaN);
});
final View mirrorView = mSurfaceControlViewHost.getView();
- assertNotNull(mirrorView);
+ assertThat(mirrorView).isNotNull();
final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
- assertNotNull(nodeInfo.getContentDescription());
- assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
- assertThat(nodeInfo.getActionList(),
- hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
- new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
- new AccessibilityAction(R.id.accessibility_action_move_right, null),
- new AccessibilityAction(R.id.accessibility_action_move_left, null),
- new AccessibilityAction(R.id.accessibility_action_move_down, null),
- new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+ assertThat(nodeInfo.getContentDescription()).isNotNull();
+ assertThat(nodeInfo.getStateDescription().toString()).contains("250");
+ assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
+ new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+ mContext.getResources().getString(
+ R.string.magnification_open_settings_click_label)),
+ new AccessibilityAction(R.id.accessibility_action_zoom_in,
+ mContext.getString(R.string.accessibility_control_zoom_in)),
+ new AccessibilityAction(R.id.accessibility_action_zoom_out,
+ mContext.getString(R.string.accessibility_control_zoom_out)),
+ new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)),
+ new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)),
+ new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)),
+ new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up))));
}
@Test
@@ -791,28 +793,33 @@
});
final View mirrorView = mSurfaceControlViewHost.getView();
- assertTrue(
- mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+ assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
+ .isTrue();
// Minimum scale is 1.0.
verify(mWindowMagnifierCallback).onPerformScaleAction(
eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
- assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+ assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
+ .isTrue();
verify(mWindowMagnifierCallback).onPerformScaleAction(
eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
// TODO: Verify the final state when the mirror surface is visible.
- assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
- assertTrue(
- mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
- assertTrue(
- mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
- assertTrue(
- mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+ assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
+ .isTrue();
+ assertThat(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
+ .isTrue();
+ assertThat(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
+ .isTrue();
+ assertThat(
+ mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
+ .isTrue();
verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
- assertTrue(mirrorView.performAccessibilityAction(
- AccessibilityAction.ACTION_CLICK.getId(), null));
+ assertThat(mirrorView.performAccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
}
@@ -844,22 +851,22 @@
View topRightCorner = getInternalView(R.id.top_right_corner);
View topLeftCorner = getInternalView(R.id.top_left_corner);
- assertEquals(View.VISIBLE, closeButton.getVisibility());
- assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
- assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
- assertEquals(View.VISIBLE, topRightCorner.getVisibility());
- assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+ assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
final View mirrorView = mSurfaceControlViewHost.getView();
mInstrumentation.runOnMainSync(() ->
mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
null));
- assertEquals(View.GONE, closeButton.getVisibility());
- assertEquals(View.GONE, bottomRightCorner.getVisibility());
- assertEquals(View.GONE, bottomLeftCorner.getVisibility());
- assertEquals(View.GONE, topRightCorner.getVisibility());
- assertEquals(View.GONE, topLeftCorner.getVisibility());
+ assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
+ assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
+ assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
+ assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
+ assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -901,8 +908,8 @@
int newWindowWidth =
(int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ 2 * mirrorSurfaceMargin;
- assertEquals(newWindowWidth, actualWindowWidth.get());
- assertEquals(startingHeight, actualWindowHeight.get());
+ assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+ assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
}
@Test
@@ -943,8 +950,8 @@
int newWindowHeight =
(int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+ 2 * mirrorSurfaceMargin;
- assertEquals(startingWidth, actualWindowWidth.get());
- assertEquals(newWindowHeight, actualWindowHeight.get());
+ assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
+ assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
}
@Test
@@ -963,8 +970,11 @@
final View mirrorView = mSurfaceControlViewHost.getView();
final AccessibilityNodeInfo accessibilityNodeInfo =
mirrorView.createAccessibilityNodeInfo();
- assertFalse(accessibilityNodeInfo.getActionList().contains(
- new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+ assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+ new AccessibilityAction(
+ R.id.accessibility_action_increase_window_width,
+ mContext.getString(
+ R.string.accessibility_control_increase_window_width)));
}
@Test
@@ -983,8 +993,9 @@
final View mirrorView = mSurfaceControlViewHost.getView();
final AccessibilityNodeInfo accessibilityNodeInfo =
mirrorView.createAccessibilityNodeInfo();
- assertFalse(accessibilityNodeInfo.getActionList().contains(
- new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+ assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+ new AccessibilityAction(
+ R.id.accessibility_action_increase_window_height, null));
}
@Test
@@ -1024,8 +1035,8 @@
int newWindowWidth =
(int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ 2 * mirrorSurfaceMargin;
- assertEquals(newWindowWidth, actualWindowWidth.get());
- assertEquals(startingSize, actualWindowHeight.get());
+ assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+ assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
}
@Test
@@ -1066,8 +1077,8 @@
int newWindowHeight =
(int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+ 2 * mirrorSurfaceMargin;
- assertEquals(startingSize, actualWindowWidth.get());
- assertEquals(newWindowHeight, actualWindowHeight.get());
+ assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+ assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
}
@Test
@@ -1086,8 +1097,9 @@
final View mirrorView = mSurfaceControlViewHost.getView();
final AccessibilityNodeInfo accessibilityNodeInfo =
mirrorView.createAccessibilityNodeInfo();
- assertFalse(accessibilityNodeInfo.getActionList().contains(
- new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+ assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+ new AccessibilityAction(
+ R.id.accessibility_action_decrease_window_width, null));
}
@Test
@@ -1106,8 +1118,9 @@
final View mirrorView = mSurfaceControlViewHost.getView();
final AccessibilityNodeInfo accessibilityNodeInfo =
mirrorView.createAccessibilityNodeInfo();
- assertFalse(accessibilityNodeInfo.getActionList().contains(
- new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+ assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+ new AccessibilityAction(
+ R.id.accessibility_action_decrease_window_height, null));
}
@Test
@@ -1117,8 +1130,8 @@
Float.NaN);
});
- assertEquals(getContext().getResources().getString(
- com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
+ assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
+ com.android.internal.R.string.android_system_label));
}
@Test
@@ -1133,14 +1146,14 @@
Float.NaN);
});
- assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
+ assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
}
@Test
public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
// the config orientation should not be undefined, since it would cause config.diff
// returning 0 and thus the orientation changed would not be detected
- assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+ assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
final Configuration config = mResources.getConfiguration();
config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
@@ -1151,7 +1164,7 @@
() -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
Float.NaN, Float.NaN));
- assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
}
@Test
@@ -1179,7 +1192,7 @@
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
});
- assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
+ assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
}
@Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
@@ -1202,10 +1215,10 @@
// maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
final double oldMax = maxScaleX.get();
final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
- assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+ assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
});
- assertTrue(maxScaleX.get() > 1.0);
+ assertThat(maxScaleX.get()).isGreaterThan(1.0);
}
@Test
@@ -1250,7 +1263,7 @@
View dragButton = getInternalView(R.id.drag_handle);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
- assertEquals(Gravity.BOTTOM | Gravity.LEFT, params.gravity);
+ assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
}
@Test
@@ -1279,7 +1292,7 @@
View dragButton = getInternalView(R.id.drag_handle);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
- assertEquals(Gravity.BOTTOM | Gravity.RIGHT, params.gravity);
+ assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
}
@Test
@@ -1301,8 +1314,8 @@
});
- assertEquals(expectedWindowHeight, actualWindowHeight.get());
- assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+ assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
}
@Test
@@ -1322,8 +1335,8 @@
actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(expectedWindowHeight, actualWindowHeight.get());
- assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+ assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
}
@Test
@@ -1343,8 +1356,8 @@
actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(minimumWindowSize, actualWindowHeight.get());
- assertEquals(minimumWindowSize, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
+ assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
}
@Test
@@ -1362,8 +1375,8 @@
actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(bounds.height(), actualWindowHeight.get());
- assertEquals(bounds.width(), actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
+ assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
}
@Test
@@ -1395,8 +1408,8 @@
mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(expectedWindowHeight, actualWindowHeight.get());
- assertEquals(expectedWindowWidth, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+ assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
}
@Test
@@ -1431,8 +1444,8 @@
mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(startingSize + 1, actualWindowHeight.get());
- assertEquals(startingSize + 2, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+ assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
}
@Test
@@ -1460,8 +1473,8 @@
actualWindowWidth.set(
mSurfaceControlViewHost.getView().getLayoutParams().width);
});
- assertEquals(startingSize + 1, actualWindowHeight.get());
- assertEquals(startingSize, actualWindowWidth.get());
+ assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+ assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
}
@Test
@@ -1483,8 +1496,8 @@
magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
});
- assertTrue(magnificationCenterX.get() < bounds.right);
- assertTrue(magnificationCenterY.get() < bounds.bottom);
+ assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
+ assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
}
@Test
@@ -1510,7 +1523,7 @@
private <T extends View> T getInternalView(@IdRes int idRes) {
View mirrorView = mSurfaceControlViewHost.getView();
T view = mirrorView.findViewById(idRes);
- assertNotNull(view);
+ assertThat(view).isNotNull();
return view;
}
@@ -1519,14 +1532,14 @@
return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
}
- private CharSequence getAccessibilityWindowTitle() {
+ private String getAccessibilityWindowTitle() {
final View mirrorView = mSurfaceControlViewHost.getView();
if (mirrorView == null) {
return null;
}
WindowManager.LayoutParams layoutParams =
(WindowManager.LayoutParams) mirrorView.getLayoutParams();
- return layoutParams.accessibilityTitle;
+ return layoutParams.accessibilityTitle.toString();
}
private boolean hasMagnificationOverlapFlag() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 14837f2..6e883c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -16,12 +16,28 @@
package com.android.systemui.keyboard.shortcut.data.repository
+import android.hardware.input.fakeInputManager
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.KEYCODE_B
+import android.view.KeyEvent.KEYCODE_C
+import android.view.KeyEvent.KEYCODE_D
+import android.view.KeyEvent.KEYCODE_E
+import android.view.KeyEvent.KEYCODE_F
+import android.view.KeyEvent.KEYCODE_G
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
@@ -47,13 +63,14 @@
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeAppCategoriesSource = FakeKeyboardShortcutGroupsSource()
private val kosmos =
testKosmos().also {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = fakeSystemSource
it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
- it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperAppCategoriesShortcutsSource = fakeAppCategoriesSource
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
@@ -61,6 +78,7 @@
private val repo = kosmos.shortcutHelperCategoriesRepository
private val helper = kosmos.shortcutHelperTestHelper
private val testScope = kosmos.testScope
+ private val fakeInputManager = kosmos.fakeInputManager
@Before
fun setUp() {
@@ -87,4 +105,74 @@
// though fetching shortcuts again would have returned a new result.
assertThat(secondCategories).isEqualTo(firstCategories)
}
+
+ @Test
+ fun categories_filtersShortcutsWithUnsupportedKeyCodes() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(
+ listOf(
+ simpleGroup(
+ simpleShortcutInfo(KEYCODE_A),
+ simpleShortcutInfo(KEYCODE_B),
+ ),
+ simpleGroup(
+ simpleShortcutInfo(KEYCODE_C),
+ ),
+ )
+ )
+ fakeMultiTaskingSource.setGroups(
+ listOf(
+ simpleGroup(
+ simpleShortcutInfo(KEYCODE_D),
+ ),
+ simpleGroup(
+ simpleShortcutInfo(KEYCODE_E),
+ simpleShortcutInfo(KEYCODE_F),
+ ),
+ )
+ )
+ fakeAppCategoriesSource.setGroups(listOf(simpleGroup(simpleShortcutInfo(KEYCODE_G))))
+
+ fakeInputManager.removeKeysFromKeyboard(deviceId = 123, KEYCODE_A, KEYCODE_D, KEYCODE_G)
+ helper.toggle(deviceId = 123)
+
+ val categories by collectLastValue(repo.categories)
+ assertThat(categories)
+ .containsExactly(
+ ShortcutCategory(
+ ShortcutCategoryType.System,
+ listOf(
+ simpleSubCategory(simpleShortcut("B")),
+ simpleSubCategory(simpleShortcut("C")),
+ )
+ ),
+ ShortcutCategory(
+ ShortcutCategoryType.MultiTasking,
+ listOf(
+ simpleSubCategory(
+ simpleShortcut("E"),
+ simpleShortcut("F"),
+ ),
+ )
+ ),
+ )
+ }
+
+ private fun simpleSubCategory(vararg shortcuts: Shortcut) =
+ ShortcutSubCategory(simpleGroupLabel, shortcuts.asList())
+
+ private fun simpleShortcut(vararg keys: String) =
+ Shortcut(
+ label = simpleShortcutLabel,
+ commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) }))
+ )
+
+ private fun simpleGroup(vararg shortcuts: KeyboardShortcutInfo) =
+ KeyboardShortcutGroup(simpleGroupLabel, shortcuts.asList())
+
+ private fun simpleShortcutInfo(keyCode: Int = 0) =
+ KeyboardShortcutInfo(simpleShortcutLabel, keyCode, /* modifiers= */ 0)
+
+ private val simpleShortcutLabel = "shortcut label"
+ private val simpleGroupLabel = "group label"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 69fc463..0043173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -23,7 +26,12 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -231,4 +239,126 @@
assertThat(activeUiState.defaultSelectedCategory)
.isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
}
+
+ @Test
+ fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(
+ groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+ groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+ )
+ fakeMultiTaskingSource.setGroups(
+ groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+ )
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+ viewModel.onSearchQueryChanged("foo")
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.shortcutCategories)
+ .containsExactly(
+ ShortcutCategory(
+ System,
+ subCategoryWithShortcutLabels("first Foo shortcut1"),
+ subCategoryWithShortcutLabels("second foO shortcut2")
+ ),
+ ShortcutCategory(
+ MultiTasking,
+ subCategoryWithShortcutLabels("third FoO shortcut1")
+ )
+ )
+ }
+
+ @Test
+ fun shortcutsUiState_userTypedQuery_noMatch_returnsEmptyList() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(
+ groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+ groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+ )
+ fakeMultiTaskingSource.setGroups(
+ groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+ )
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+ viewModel.onSearchQueryChanged("unmatched query")
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.shortcutCategories).isEmpty()
+ }
+
+ @Test
+ fun shortcutsUiState_userTypedQuery_noMatch_returnsNullDefaultSelectedCategory() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(
+ groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+ groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+ )
+ fakeMultiTaskingSource.setGroups(
+ groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+ )
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+ viewModel.onSearchQueryChanged("unmatched query")
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory).isNull()
+ }
+
+ @Test
+ fun shortcutsUiState_userTypedQuery_changesDefaultSelectedCategoryToFirstMatchingCategory() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+ fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+ viewModel.onSearchQueryChanged("second")
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory).isEqualTo(MultiTasking)
+ }
+
+ @Test
+ fun shortcutsUiState_userTypedQuery_multipleCategoriesMatch_currentAppIsDefaultSelected() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+ fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+ fakeCurrentAppsSource.setGroups(groupWithShortcutLabels("third shortcut"))
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+ viewModel.onSearchQueryChanged("shortcut")
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
+ }
+
+ private fun groupWithShortcutLabels(vararg shortcutLabels: String) =
+ KeyboardShortcutGroup(SIMPLE_GROUP_LABEL, shortcutLabels.map { simpleShortcutInfo(it) })
+ .apply { packageName = "test.package.name" }
+
+ private fun simpleShortcutInfo(label: String) =
+ KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)
+
+ private fun subCategoryWithShortcutLabels(vararg shortcutLabels: String) =
+ ShortcutSubCategory(
+ label = SIMPLE_GROUP_LABEL,
+ shortcuts = shortcutLabels.map { simpleShortcut(it) },
+ )
+
+ private fun simpleShortcut(label: String) =
+ shortcut(label) {
+ command {
+ key("Ctrl")
+ key("A")
+ }
+ }
+
+ companion object {
+ private const val SIMPLE_GROUP_LABEL = "simple group"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 8bc0a60..a310520 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -20,21 +20,24 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -44,13 +47,12 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnableSceneContainer
@RunWith(AndroidJUnit4::class)
class KeyguardDismissActionInteractorTest : SysuiTestCase() {
val kosmos = testKosmos()
private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
-
private val testScope = kosmos.testScope
private lateinit var dismissInteractorWithDependencies:
@@ -74,6 +76,8 @@
transitionInteractor = kosmos.keyguardTransitionInteractor,
dismissInteractor = dismissInteractorWithDependencies.interactor,
applicationScope = testScope.backgroundScope,
+ sceneInteractor = kosmos.sceneInteractor,
+ deviceEntryInteractor = kosmos.deviceEntryInteractor,
)
}
@@ -158,7 +162,6 @@
}
@Test
- @DisableSceneContainer
fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
testScope.runTest {
val executeDismissAction by collectLastValue(underTest.executeDismissAction)
@@ -175,33 +178,6 @@
)
assertThat(executeDismissAction).isNull()
- // WHEN the keyguard is GONE
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope,
- )
- assertThat(executeDismissAction).isNotNull()
- }
-
- @Test
- @EnableSceneContainer
- fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() =
- testScope.runTest {
- val executeDismissAction by collectLastValue(underTest.executeDismissAction)
-
- // WHEN a keyguard action will run after the keyguard is gone
- val onDismissAction = {}
- keyguardRepository.setDismissAction(
- DismissAction.RunAfterKeyguardGone(
- dismissAction = onDismissAction,
- onCancelAction = {},
- message = "message",
- willAnimateOnLockscreen = true,
- )
- )
- assertThat(executeDismissAction).isNull()
-
kosmos.setSceneTransition(Idle(Scenes.Gone))
assertThat(executeDismissAction).isNotNull()
@@ -210,8 +186,8 @@
@Test
fun resetDismissAction() =
testScope.runTest {
+ kosmos.setSceneTransition(Idle(Scenes.Bouncer))
val resetDismissAction by collectLastValue(underTest.resetDismissAction)
-
keyguardRepository.setDismissAction(
DismissAction.RunAfterKeyguardGone(
dismissAction = {},
@@ -220,15 +196,40 @@
willAnimateOnLockscreen = true,
)
)
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- testScope
- )
+ assertThat(resetDismissAction).isNull()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
assertThat(resetDismissAction).isEqualTo(Unit)
}
@Test
+ fun doNotResetDismissActionOnUnlockedShade() =
+ testScope.runTest {
+ kosmos.setSceneTransition(Idle(Scenes.Bouncer))
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+ keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = {},
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(resetDismissAction).isNull()
+
+ kosmos.setSceneTransition(
+ Transition(
+ from = Scenes.Bouncer,
+ to = Scenes.NotificationsShade,
+ progress = flowOf(1f),
+ )
+ )
+ assertThat(resetDismissAction).isNull()
+ }
+
+ @Test
fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
testScope.runTest {
val dismissAction by collectLastValue(underTest.dismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 8d217fc..a5fbfb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -17,11 +17,12 @@
package com.android.systemui.screenshot.policy
import android.content.ComponentName
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.content.Context
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.DisplayContentModel
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -49,16 +50,30 @@
import com.android.window.flags.Flags
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
@RunWith(AndroidJUnit4::class)
class WorkProfilePolicyTest {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule()
+
+ @JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock lateinit var mContext: Context
private val kosmos = Kosmos()
- private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+ private lateinit var policy: WorkProfilePolicy
+
+ @Before
+ fun setUp() {
+ policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
+ }
/**
* There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 7f981ee..6a5976e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
@@ -36,7 +37,7 @@
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -55,6 +56,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarModeRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
private val testScope = TestScope()
private val commandQueue = mock<CommandQueue>()
private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
@@ -63,7 +65,7 @@
mock<StatusBarFragmentComponent>().also {
whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
}
- private val ongoingCallRepository = OngoingCallRepository()
+ private val ongoingCallRepository = kosmos.ongoingCallRepository
private val underTest =
StatusBarModePerDisplayRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index e738b61..c005743 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -108,6 +109,7 @@
private val dragController: ExpandableNotificationRowDragController = mock()
private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
private val statusBarService: IStatusBarService = mock()
+ private val uiEventLogger: UiEventLogger = mock()
private lateinit var controller: ExpandableNotificationRowController
@Before
@@ -147,7 +149,8 @@
settingsController,
dragController,
dismissibilityProvider,
- statusBarService
+ statusBarService,
+ uiEventLogger
)
whenever(view.childrenContainer).thenReturn(childrenContainer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 2bb610a..699e8c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -642,7 +642,7 @@
return spy(NotificationContentView(mContext, /* attrs= */ null))
.apply {
- initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock())
+ initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock(), mock())
setContainingNotification(row)
setHeights(
/* smallHeight= */ contractedHeight,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index c74a04f..d7fdce2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -54,6 +54,7 @@
import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.TestScopeProvider;
import com.android.systemui.TestableDependency;
@@ -671,7 +672,8 @@
mock(SmartReplyConstants.class),
mock(SmartReplyController.class),
mFeatureFlags,
- mock(IStatusBarService.class));
+ mock(IStatusBarService.class),
+ mock(UiEventLogger.class));
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b7ebebe..ad029d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -92,9 +92,12 @@
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
- private val bigGap = px(R.dimen.notification_section_divider_height)
- private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
+ private val notifSectionDividerGap = px(R.dimen.notification_section_divider_height)
private val scrimPadding = px(R.dimen.notification_side_paddings)
+ private val baseZ by lazy { ambientState.baseZHeight }
+ private val headsUpZ = px(R.dimen.heads_up_pinned_elevation)
+ private val bigGap = notifSectionDividerGap
+ private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
@Before
fun setUp() {
@@ -219,6 +222,8 @@
// Then: HUN is at the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is not elevated
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
// And: HUN has its full height
assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHeight)
}
@@ -243,6 +248,8 @@
// Then: HUN is translated to the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is not elevated
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ)
// And: HUN is clipped to the available space
// newTranslation = max(150, -25)
// distToReal = 150 - (-25)
@@ -270,6 +277,8 @@
// Then: HUN is translated to the headsUpTop
assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN fully elevated to baseZ + headsUpZ
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
// And: HUN is clipped to its collapsed height
assertThat(notificationRow.viewState.height).isEqualTo(collapsedHeight)
}
@@ -279,16 +288,100 @@
fun resetViewStates_defaultHun_showingQS_hunTranslatedToHeadsUpTop() {
// Given: the shade is open and scrolled to the bottom to show the QuickSettings
val headsUpTop = 2000f
+ val intrinsicHunHeight = 300
fakeHunInShade(
headsUpTop = headsUpTop,
stackTop = 2600f, // stack scrolled below the screen
stackCutoff = 4000f,
collapsedHeight = 100,
- intrinsicHeight = 300
+ intrinsicHeight = intrinsicHunHeight,
)
whenever(notificationRow.isAboveShelf).thenReturn(true)
- resetViewStates_hunYTranslationIs(headsUpTop)
+ // When
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ // Then: HUN is translated to the headsUpTop
+ assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+ // And: HUN is elevated to baseZ + headsUpZ
+ assertThat(notificationRow.viewState.zTranslation).isEqualTo(baseZ + headsUpZ)
+ // And: HUN maintained its full height
+ assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHunHeight)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_fullOverlap_hunHasFullElevation() {
+ // Given: the overlap equals to the top content padding
+ val contentTop = 280f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is fully elevated to baseZ + headsUpZ
+ assertThat(viewState.zTranslation).isEqualTo(headsUpZ)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_someOverlap_hunIsPartlyElevated() {
+ // Given: the overlap is bigger than zero, but less than the top content padding
+ val contentTop = 290f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is partly elevated
+ assertThat(viewState.zTranslation).apply {
+ isGreaterThan(0f)
+ isLessThan(headsUpZ)
+ }
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateZTranslationForHunInStack_noOverlap_hunIsNotElevated() {
+ // Given: no overlap between the content and the HUN
+ val contentTop = 300f
+ val contentTopPadding = 20f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = 200f
+ }
+
+ // When
+ stackScrollAlgorithm.updateZTranslationForHunInStack(
+ /* scrollingContentTop = */ contentTop,
+ /* scrollingContentTopPadding */ contentTopPadding,
+ /* baseZ = */ 0f,
+ /* viewState = */ viewState,
+ )
+
+ // Then: HUN is not elevated
+ assertThat(viewState.zTranslation).isEqualTo(0f)
}
@Test
@@ -964,6 +1057,7 @@
}
@Test
+ @DisableSceneContainer
fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
// Given: shade is opened, yTranslation of HUN is 0,
// the height of HUN equals to the height of QQS Panel,
@@ -989,6 +1083,7 @@
}
@Test
+ @DisableSceneContainer
fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
// Given: shade is opened, yTranslation of HUN is greater than 0,
// the height of HUN is equal to the height of QQS Panel,
@@ -1419,14 +1514,16 @@
/** fakes the notification row under test, to be a HUN in a fully opened shade */
private fun fakeHunInShade(
- headsUpTop: Float,
collapsedHeight: Int,
intrinsicHeight: Int,
+ headsUpTop: Float,
+ headsUpBottom: Float = headsUpTop + intrinsicHeight, // assume all the space available
stackTop: Float,
stackCutoff: Float = 2000f,
fullStackHeight: Float = 3000f
) {
ambientState.headsUpTop = headsUpTop
+ ambientState.headsUpBottom = headsUpBottom
ambientState.stackTop = stackTop
ambientState.stackCutoff = stackCutoff
@@ -1438,6 +1535,7 @@
}
stackScrollAlgorithm.setIsExpanded(true)
+ whenever(notificationRow.headerVisibleAmount).thenReturn(1.0f)
whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
whenever(notificationRow.collapsedHeight).thenReturn(collapsedHeight)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 80b9e80..c523819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -27,6 +27,7 @@
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.View;
@@ -111,8 +112,8 @@
verify(mStatusBarKeyguardViewManager).showBouncer(true);
}
-
@Test
+ @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
public void onMakeExpandedVisibleForRemoteInput_collapsedGroup_expandGroupExpansion() {
// GIVEN
final Runnable onExpandedVisibleRunner = mock(Runnable.class);
@@ -137,6 +138,7 @@
}
@Test
+ @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
public void onMakeExpandedVisibleForRemoteInput_expandedGroup_setUserExpandedTrue() {
// GIVEN
final Runnable onExpandedVisibleRunner = mock(Runnable.class);
@@ -161,6 +163,7 @@
}
@Test
+ @DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
public void onMakeExpandedVisibleForRemoteInput_nonGroupNotifications_setUserExpandedTrue() {
// GIVEN
final Runnable onExpandedVisibleRunner = mock(Runnable.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 5174ec7..c4371fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -31,10 +31,11 @@
import android.widget.LinearLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
@@ -43,7 +44,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -84,13 +85,13 @@
@TestableLooper.RunWithLooper
@OptIn(ExperimentalCoroutinesApi::class)
class OngoingCallControllerTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
private val clock = FakeSystemClock()
private val mainExecutor = FakeExecutor(clock)
- private val uiEventLoggerFake = UiEventLoggerFake()
private val testScope = TestScope()
private val statusBarModeRepository = FakeStatusBarModeRepository()
- private val ongoingCallRepository = OngoingCallRepository()
+ private val ongoingCallRepository = kosmos.ongoingCallRepository
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
@@ -124,11 +125,11 @@
mockActivityStarter,
mainExecutor,
mockIActivityManager,
- OngoingCallLogger(uiEventLoggerFake),
DumpManager(),
mockStatusBarWindowController,
mockSwipeStatusBarAwayGestureHandler,
statusBarModeRepository,
+ logcatLogBuffer("OngoingCallControllerTest"),
)
controller.start()
controller.addCallback(mockOngoingCallListener)
@@ -544,18 +545,6 @@
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun chipClicked_clickEventLogged() {
- notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
-
- chipView.performClick()
-
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
- }
-
/** Regression test for b/212467440. */
@Test
@DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
@@ -571,18 +560,6 @@
}
@Test
- fun notifyChipVisibilityChanged_visibleEventLogged() {
- controller.notifyChipVisibilityChanged(true)
-
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
- }
-
- // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
- // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
-
- @Test
@DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
fun callNotificationAdded_chipIsClickable() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
deleted file mode 100644
index 5ce936d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.ongoingcall
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class OngoingCallLoggerTest : SysuiTestCase() {
- private val uiEventLoggerFake = UiEventLoggerFake()
- private val ongoingCallLogger = OngoingCallLogger(uiEventLoggerFake)
-
- @Test
- fun logChipClicked_clickEventLogged() {
- ongoingCallLogger.logChipClicked()
-
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_CLICKED.id)
- }
-
- @Test
- fun logChipVisibilityChanged_changeFromInvisibleToVisible_visibleEventLogged() {
- ongoingCallLogger.logChipVisibilityChanged(false)
- ongoingCallLogger.logChipVisibilityChanged(true)
-
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- assertThat(uiEventLoggerFake.eventId(0))
- .isEqualTo(OngoingCallLogger.OngoingCallEvents.ONGOING_CALL_VISIBLE.id)
- }
-
- @Test
- fun logChipVisibilityChanged_changeFromVisibleToInvisible_eventNotLogged() {
- // Setting the chip to visible here will trigger a log
- ongoingCallLogger.logChipVisibilityChanged(true)
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-
- ongoingCallLogger.logChipVisibilityChanged(false)
-
- // Expect that there were no new logs
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- }
-
- @Test
- fun logChipVisibilityChanged_visibleThenVisibleAgain_eventNotLogged() {
- // Setting the chip to visible here will trigger a log
- ongoingCallLogger.logChipVisibilityChanged(true)
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
-
- ongoingCallLogger.logChipVisibilityChanged(true)
-
- // Expect that there were no new logs
- assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 27c2366..cbb8fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -27,7 +28,8 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class OngoingCallRepositoryTest : SysuiTestCase() {
- private val underTest = OngoingCallRepository()
+ private val kosmos = Kosmos()
+ private val underTest = kosmos.ongoingCallRepository
@Test
fun hasOngoingCall_matchesSet() {
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index c4f93d1..6e7c05c 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -19,6 +19,8 @@
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
+import android.view.KeyEvent
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.mockito.ArgumentMatchers.anyInt
@@ -38,6 +40,12 @@
.build()
private val devices = mutableMapOf<Int, InputDevice>(VIRTUAL_KEYBOARD to virtualKeyboard)
+ private val allKeyCodes = (0..KeyEvent.MAX_KEYCODE)
+ private val supportedKeyCodesByDeviceId =
+ mutableMapOf(
+ // Mark all keys supported by default
+ VIRTUAL_KEYBOARD to allKeyCodes.toMutableSet()
+ )
val inputManager =
mock<InputManager> {
@@ -61,13 +69,31 @@
whenever(enableInputDevice(anyInt())).thenAnswer { invocation ->
setDeviceEnabled(invocation, enabled = true)
}
+ whenever(deviceHasKeys(any(), any())).thenAnswer { invocation ->
+ val deviceId = invocation.arguments[0] as Int
+ val keyCodes = invocation.arguments[1] as IntArray
+ val supportedKeyCodes = supportedKeyCodesByDeviceId[deviceId]!!
+ return@thenAnswer keyCodes.map { supportedKeyCodes.contains(it) }.toBooleanArray()
+ }
}
+ fun addPhysicalKeyboardIfNotPresent(deviceId: Int, enabled: Boolean = true) {
+ if (devices.containsKey(deviceId)) {
+ return
+ }
+ addPhysicalKeyboard(deviceId, enabled)
+ }
+
fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) {
check(id > 0) { "Physical keyboard ids have to be > 0" }
addKeyboard(id, enabled)
}
+ fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) {
+ addPhysicalKeyboardIfNotPresent(deviceId)
+ supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList())
+ }
+
private fun addKeyboard(id: Int, enabled: Boolean = true) {
devices[id] =
InputDevice.Builder()
@@ -77,6 +103,7 @@
.setEnabled(enabled)
.setKeyCharacterMap(keyCharacterMap)
.build()
+ supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet()
}
private fun InputDevice.copy(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 6ca5cd8..8b45662 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -95,7 +95,7 @@
}
fun toggle(deviceId: Int) {
- fakeInputManager.addPhysicalKeyboard(deviceId)
+ fakeInputManager.addPhysicalKeyboardIfNotPresent(deviceId)
fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
index 2bab1a4..d4cb6ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/source/FakeKeyboardShortcutGroupsSource.kt
@@ -24,6 +24,10 @@
override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> = groups
+ fun setGroups(vararg groups: KeyboardShortcutGroup) {
+ this.groups = groups.asList()
+ }
+
fun setGroups(groups: List<KeyboardShortcutGroup>) {
this.groups = groups
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2c6d44f..fe156e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -29,5 +31,7 @@
transitionInteractor = keyguardTransitionInteractor,
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 03a42bc..8e76a0b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -34,6 +34,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
@@ -46,6 +47,7 @@
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.statusbar.phone.centralSurfacesOptional
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.sysuiStatusBarStateController
val Kosmos.sceneContainerStartable by Fixture {
SceneContainerStartable(
@@ -77,5 +79,6 @@
windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
keyguardEnabledInteractor = keyguardEnabledInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
+ statusBarStateController = sysuiStatusBarStateController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 16dc50f..b8dec31 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -27,6 +27,7 @@
import androidx.core.os.bundleOf
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.TestableDependency
import com.android.systemui.classifier.FalsingManagerFake
@@ -357,7 +358,8 @@
mSmartReplyConstants,
mSmartReplyController,
featureFlags,
- Mockito.mock(IStatusBarService::class.java)
+ Mockito.mock(IStatusBarService::class.java),
+ Mockito.mock(UiEventLogger::class.java)
)
row.setAboveShelfChangedListener { aboveShelf: Boolean -> }
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
index 12014a0..faa2b33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.ongoingcall.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
val Kosmos.ongoingCallRepository: OngoingCallRepository by
- Kosmos.Fixture { OngoingCallRepository() }
+ Kosmos.Fixture { OngoingCallRepository(logcatLogBuffer("OngoingCallRepositoryKosmos")) }
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index 0a0b200..c29fb7f 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -46,7 +46,7 @@
* Write your unit test just like you would for an Android device:
```
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -66,7 +66,7 @@
* APIs available under Ravenwood are stateless by default. If your test requires explicit states (such as defining the UID you’re running under, or requiring a main `Looper` thread), add a `RavenwoodRule` to declare that:
```
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.runner.AndroidJUnit4;
@@ -165,7 +165,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = PackageManager.class)
+ @DisabledOnRavenwood(blockedBy = PackageManager.class)
public void testComplex() {
// Complex test that runs on devices, but is ignored under Ravenwood
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 458749d..f821e00 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -72,6 +72,7 @@
import android.content.pm.UserInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteStatement;
import android.os.Binder;
@@ -1461,8 +1462,8 @@
List<Integer> uids;
try {
uids = accounts.accountsDb.findAllUidGrants();
- } catch (SQLiteCantOpenDatabaseException e) {
- Log.w(TAG, "Could not delete grants for user = " + accounts.userId);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not delete grants for user = " + accounts.userId, e);
return;
}
for (int uid : uids) {
@@ -4464,6 +4465,9 @@
opPackageName,
visibleAccountTypes,
false /* includeUserManagedNotVisible */);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get accounts for user " + userId, e);
+ return new Account[]{};
} finally {
restoreCallingIdentity(identityToken);
}
@@ -4539,7 +4543,7 @@
try {
return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
opPackageName, false /* includeUserManagedNotVisible */);
- } catch (SQLiteCantOpenDatabaseException e) {
+ } catch (SQLiteException e) {
Log.e(TAG, "Could not get accounts for user " + userId, e);
return new Account[]{};
}
@@ -4549,7 +4553,7 @@
private Account[] getAccountsOrEmptyArray(String type, int userId, String opPackageName) {
try {
return getAccountsAsUser(type, userId, opPackageName);
- } catch (SQLiteCantOpenDatabaseException e) {
+ } catch (SQLiteException e) {
Log.w(TAG, "Could not get accounts for user " + userId, e);
return new Account[]{};
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 8ec835b..fe73bfe 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -269,6 +269,7 @@
private final ContentResolver mContentResolver;
private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
+ private final UserManager mUserManager;
private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
@@ -291,6 +292,7 @@
super(handler);
mContentResolver = context.getContentResolver();
mCallbacks = callbacks;
+ mUserManager = context.getSystemService(UserManager.class);
final boolean hasFingerprint = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -301,14 +303,6 @@
mUseLegacyFaceOnlySettings =
Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
&& hasFace && !hasFingerprint;
- mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser(
- mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
- DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0);
- mMandatoryBiometricsRequirementsSatisfied.put(context.getUserId(),
- Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
- DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
- context.getUserId()) != 0);
addBiometricListenersForMandatoryBiometrics(context);
updateContentObserver();
@@ -391,18 +385,9 @@
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
userId) != 0);
} else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
- mMandatoryBiometricsEnabled.put(userId, Settings.Secure.getIntForUser(
- mContentResolver,
- Settings.Secure.MANDATORY_BIOMETRICS,
- DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */,
- userId) != 0);
+ updateMandatoryBiometricsForAllProfiles();
} else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
- mMandatoryBiometricsRequirementsSatisfied.put(userId, Settings.Secure.getIntForUser(
- mContentResolver,
- Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
- DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
- ? 1 : 0 /* default */,
- userId) != 0);
+ updateMandatoryBiometricsRequirementsForAllProfiles();
}
}
@@ -445,6 +430,12 @@
}
public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
+ if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
+ updateMandatoryBiometricsForAllProfiles();
+ }
+ if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
+ updateMandatoryBiometricsRequirementsForAllProfiles();
+ }
return mMandatoryBiometricsEnabled.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
@@ -464,6 +455,28 @@
}
}
+ private void updateMandatoryBiometricsForAllProfiles() {
+ final int mainUserId = mUserManager.getMainUser().getIdentifier();
+ for (UserHandle userHandle: mUserManager.getUserProfiles()) {
+ mMandatoryBiometricsEnabled.put(userHandle.getIdentifier(),
+ Settings.Secure.getIntForUser(
+ mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+ mainUserId) != 0);
+ }
+ }
+
+ private void updateMandatoryBiometricsRequirementsForAllProfiles() {
+ final int mainUserId = mUserManager.getMainUser().getIdentifier();
+ for (UserHandle userHandle: mUserManager.getUserProfiles()) {
+ mMandatoryBiometricsRequirementsSatisfied.put(userHandle.getIdentifier(),
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
+ mainUserId) != 0);
+ }
+ }
+
private void addBiometricListenersForMandatoryBiometrics(Context context) {
final FingerprintManager fingerprintManager = context.getSystemService(
FingerprintManager.class);
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 7c93c8b..5c939bc 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -202,7 +202,7 @@
break;
case STATE_HIDE_IME_EXPLICIT:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userId, statsToken);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
0 /* flags */, null /* resultReceiver */, reason, userId);
@@ -210,7 +210,7 @@
break;
case STATE_HIDE_IME_NOT_ALWAYS:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userId, statsToken);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason,
@@ -221,7 +221,7 @@
if (Flags.refactorInsetsController()) {
// This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to
// set the requestedVisibleTypes in InsetsController first, before applying it.
- setImeVisibilityOnFocusedWindowClient(true, userId);
+ setImeVisibilityOnFocusedWindowClient(true, userId, statsToken);
} else {
mService.showCurrentInputLocked(windowToken, statsToken,
InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN,
@@ -278,14 +278,17 @@
}
@GuardedBy("ImfLock.class")
- private void setImeVisibilityOnFocusedWindowClient(boolean visibility, @UserIdInt int userId) {
+ private void setImeVisibilityOnFocusedWindowClient(boolean visibility, @UserIdInt int userId,
+ @NonNull ImeTracker.Token statsToken) {
final var userData = mService.getUserData(userId);
if (userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility);
+ userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility,
+ statsToken);
} else {
- // TODO(b/329229469): ImeTracker?
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index eada288..650ea60 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -25,6 +25,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.InputBindResult;
@@ -250,18 +251,18 @@
}
@AnyThread
- void setImeVisibility(boolean visible) {
+ void setImeVisibility(boolean visible, @Nullable ImeTracker.Token statsToken) {
if (mIsProxy) {
- setImeVisibilityInternal(visible);
+ setImeVisibilityInternal(visible, statsToken);
} else {
- mHandler.post(() -> setImeVisibilityInternal(visible));
+ mHandler.post(() -> setImeVisibilityInternal(visible, statsToken));
}
}
@AnyThread
- private void setImeVisibilityInternal(boolean visible) {
+ private void setImeVisibilityInternal(boolean visible, @Nullable ImeTracker.Token statsToken) {
try {
- mTarget.setImeVisibility(visible);
+ mTarget.setImeVisibility(visible, statsToken);
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a9723cc..eed34b8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1429,12 +1429,12 @@
void registerImeRequestedChangedListener() {
mWindowManagerInternal.setOnImeRequestedChangedListener(
- (windowToken, imeVisible) -> {
+ (windowToken, imeVisible, statsToken) -> {
if (Flags.refactorInsetsController()) {
if (imeVisible) {
- showCurrentInputInternal(windowToken);
+ showCurrentInputInternal(windowToken, statsToken);
} else {
- hideCurrentInputInternal(windowToken);
+ hideCurrentInputInternal(windowToken, statsToken);
}
}
});
@@ -1887,7 +1887,12 @@
if (Flags.refactorInsetsController()) {
if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindow != null) {
- showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow);
+ // Re-use current statsToken, if it exists.
+ final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken
+ : createStatsTokenForFocusedClient(true /* show */,
+ SoftInputShowHideReason.ATTACH_NEW_INPUT, userId);
+ userData.mCurStatsToken = null;
+ showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow, statsToken);
}
} else {
if (isShowRequestedForCurrentWindow(userId)) {
@@ -3099,7 +3104,7 @@
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true);
+ .setImeVisibility(true, statsToken);
if (resultReceiver != null) {
resultReceiver.send(
wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
@@ -3119,7 +3124,8 @@
}
}
- boolean showCurrentInputInternal(IBinder windowToken) {
+ // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
+ boolean showCurrentInputInternal(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showCurrentInputInternal");
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
@@ -3129,7 +3135,7 @@
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- return showCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */,
+ return showCurrentInputLocked(windowToken, statsToken, 0 /* flags */,
0 /* lastClickTooType */, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT, userId);
} finally {
@@ -3139,7 +3145,8 @@
}
}
- boolean hideCurrentInputInternal(IBinder windowToken) {
+ // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
+ boolean hideCurrentInputInternal(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideCurrentInputInternal");
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
@@ -3149,7 +3156,7 @@
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
- return hideCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */,
+ return hideCurrentInputLocked(windowToken, statsToken, 0 /* flags */,
null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT,
userId);
} finally {
@@ -3536,7 +3543,7 @@
boolean wasVisible = isInputShownLocked();
// TODO add windowToken to interface
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false);
+ .setImeVisibility(false, statsToken);
if (resultReceiver != null) {
resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
@@ -4855,13 +4862,13 @@
final long ident = Binder.clearCallingIdentity();
try {
if (Flags.refactorInsetsController()) {
- userData.mCurClient.mClient.setImeVisibility(false);
+ userData.mCurClient.mClient.setImeVisibility(false, statsToken);
// TODO we will loose the flags here
if (userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false);
+ .setImeVisibility(false, statsToken);
}
} else {
hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
@@ -4894,13 +4901,13 @@
final long ident = Binder.clearCallingIdentity();
try {
if (Flags.refactorInsetsController()) {
- userData.mCurClient.mClient.setImeVisibility(false);
+ userData.mCurClient.mClient.setImeVisibility(false, statsToken);
// TODO we will loose the flags here
if (userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true);
+ .setImeVisibility(true, statsToken);
}
} else {
showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
@@ -5074,7 +5081,8 @@
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false);
+ .setImeVisibility(false,
+ null /* TODO(b329229469) check statsToken */);
}
} else {
@SoftInputShowHideReason final int reason = (int) msg.obj;
@@ -6318,6 +6326,9 @@
if (Flags.imeSwitcherRevamp()) {
p.println(" menuControllerNew:");
mMenuControllerNew.dump(p, " ");
+ } else {
+ p.println(" menuController:");
+ mMenuController.dump(p, " ");
}
p.println(" mCurToken=" + bindingController.getCurToken());
p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
@@ -6822,7 +6833,8 @@
&& userData.mImeBindingState.mFocusedWindowClient.mClient
!= null) {
userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false);
+ .setImeVisibility(false,
+ null /* TODO(b329229469) initialize statsToken here? */);
} else {
// TODO(b329229469): ImeTracker?
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 06f73f3..ba5c13e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -28,6 +28,7 @@
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Printer;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
@@ -58,6 +59,7 @@
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
private View mSwitchingDialogTitleView;
+ private List<ImeSubtypeListItem> mImList;
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
@@ -97,6 +99,7 @@
// Find out which item should be checked by default.
final int size = imList.size();
+ mImList = imList;
mIms = new InputMethodInfo[size];
mSubtypeIds = new int[size];
// No items are checked by default. When we have a list of explicitly enabled subtypes,
@@ -244,7 +247,9 @@
mService.updateSystemUiLocked(userId);
mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
+ mImList = null;
mIms = null;
+ mSubtypeIds = null;
}
}
@@ -277,6 +282,15 @@
}
}
+ void dump(@NonNull Printer pw, @NonNull String prefix) {
+ final boolean showing = isisInputMethodPickerShownForTestLocked();
+ pw.println(prefix + " isShowing: " + showing);
+
+ if (showing) {
+ pw.println(prefix + " imList: " + mImList);
+ }
+ }
+
private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
private final LayoutInflater mInflater;
private final int mTextViewResourceId;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index 2bb3be6..f2714db 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -17,12 +17,10 @@
package com.android.server.location.contexthub;
import android.chre.flags.Flags;
-import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.Callable;
+import java.util.Random;
/**
* A class to manage behaviors during test mode. This is used for testing.
@@ -31,31 +29,32 @@
public class ContextHubTestModeManager {
private static final String TAG = "ContextHubTestModeManager";
- private static final int DROP_MESSAGE_TO_HOST_EVENT = 0;
- private static final int DROP_MESSAGE_TO_CONTEXT_HUB_EVENT = 1;
- private static final int DUPLICATE_MESSAGE_TO_HOST_EVENT = 2;
- private static final int DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT = 3;
- private static final int NUMBER_OF_EVENTS = 4;
+ /** Probability of duplicating a message. */
+ private static final double MESSAGE_DROP_PROBABILITY = 0.05;
+
+ /** Probability of duplicating a message. */
+ private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
/** The number of total messages to send when the duplication event happens. */
private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
- /** The counter to track the number of interactions with the test mode manager. */
- private final AtomicLong mCounter = new AtomicLong(0);
+ /**
+ * The seed for the random number generator. This is used to make the
+ * test more deterministic.
+ */
+ private static final long SEED = 0xDEADBEEF;
+
+ private final Random mRandom = new Random(SEED);
/**
* @return whether the message was handled
* @see ContextHubServiceCallback#handleNanoappMessage
*/
public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
- if (!message.isReliable()) {
- return false;
- }
-
- long counterValue = mCounter.getAndIncrement();
if (Flags.reliableMessageDuplicateDetectionService()
- && counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_HOST_EVENT) {
- Log.i(TAG, "[TEST MODE] Duplicating message to host ("
+ && message.isReliable()
+ && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
+ Log.i(TAG, "[TEST MODE] Duplicating message ("
+ NUM_MESSAGES_TO_DUPLICATE
+ " sends) with message sequence number: "
+ message.getMessageSequenceNumber());
@@ -64,14 +63,6 @@
}
return true;
}
-
- if (counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_HOST_EVENT) {
- Log.i(TAG, "[TEST MODE] Dropping message to host with "
- + "message sequence number: "
- + message.getMessageSequenceNumber());
- return true;
- }
-
return false;
}
@@ -79,39 +70,14 @@
* @return whether the message was handled
* @see IContextHubWrapper#sendMessageToContextHub
*/
- public boolean sendMessageToContextHub(Callable<Integer> sendMessage, NanoAppMessage message) {
- if (!message.isReliable()) {
- return false;
- }
-
- long counterValue = mCounter.getAndIncrement();
- if (counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT) {
- Log.i(TAG, "[TEST MODE] Duplicating message to the Context Hub ("
- + NUM_MESSAGES_TO_DUPLICATE
- + " sends) with message sequence number: "
- + message.getMessageSequenceNumber());
- for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
- try {
- int result = sendMessage.call();
- if (result != ContextHubTransaction.RESULT_SUCCESS) {
- Log.e(TAG, "sendMessage returned an error: " + result);
- }
- } catch (Exception e) {
- Log.e(TAG, "Exception in sendMessageToContextHub: "
- + e.getMessage());
- }
- }
- return true;
- }
-
+ public boolean sendMessageToContextHub(NanoAppMessage message) {
if (Flags.reliableMessageRetrySupportService()
- && counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_CONTEXT_HUB_EVENT) {
- Log.i(TAG, "[TEST MODE] Dropping message to the Context Hub with "
- + "message sequence number: "
+ && message.isReliable()
+ && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
+ Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+ message.getMessageSequenceNumber());
return true;
}
-
return false;
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index a8ad418..4fc3d87 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -53,7 +53,6 @@
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.Callable;
/**
* @hide
@@ -660,40 +659,32 @@
@ContextHubTransaction.Result
public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
- NanoAppMessage message) {
+ NanoAppMessage message) throws RemoteException {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
- Callable<Integer> sendMessage = () -> {
- try {
- var msg = ContextHubServiceUtil.createAidlContextHubMessage(
- hostEndpointId, message);
- hub.sendMessageToHub(contextHubId, msg);
- return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
- return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } catch (IllegalArgumentException e) {
- return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
- }
- };
+ try {
+ var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+ hostEndpointId, message);
- // Only process the message normally if not using test mode manager or if
- // the test mode manager call returned false as this indicates it did not
- // process the message.
- boolean useTestModeManager = Flags.reliableMessageImplementation()
- && Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get();
- if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(
- sendMessage, message)) {
- try {
- return sendMessage.call();
- } catch (Exception e) {
- return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ // Only process the message normally if not using test mode manager or if
+ // the test mode manager call returned false as this indicates it did not
+ // process the message.
+ boolean useTestModeManager = Flags.reliableMessageImplementation()
+ && Flags.reliableMessageTestModeBehavior()
+ && mIsTestModeEnabled.get();
+ if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
+ hub.sendMessageToHub(contextHubId, msg);
}
+
+ return ContextHubTransaction.RESULT_SUCCESS;
+ } catch (RemoteException | ServiceSpecificException e) {
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
- return ContextHubTransaction.RESULT_SUCCESS;
}
@ContextHubTransaction.Result
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 9b64488..40b2ff9 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -16,6 +16,8 @@
package com.android.server.policy;
+import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
+
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.role.RoleManager;
@@ -82,6 +84,10 @@
private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
+ private final SparseArray<ComponentName> mComponentShortcuts = new SparseArray<>();
+ private final SparseArray<ComponentName> mShiftComponentShortcuts = new SparseArray<>();
+ private final Map<ComponentName, Intent> mComponentIntents =
+ new HashMap<ComponentName, Intent>();
private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
@@ -115,23 +121,31 @@
private final Context mContext;
private final Handler mHandler;
- private final RoleManager mRoleManager;
- private final PackageManager mPackageManager;
private boolean mSearchKeyShortcutPending = false;
private boolean mConsumeSearchKeyUp = true;
+ private UserHandle mCurrentUser;
- ModifierShortcutManager(Context context, Handler handler) {
+ ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
mContext = context;
mHandler = handler;
- mPackageManager = mContext.getPackageManager();
- mRoleManager = mContext.getSystemService(RoleManager.class);
- mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+ RoleManager rm = mContext.getSystemService(RoleManager.class);
+ rm.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
(String roleName, UserHandle user) -> {
mRoleIntents.remove(roleName);
}, UserHandle.ALL);
+ mCurrentUser = currentUser;
loadShortcuts();
}
+ void setCurrentUser(UserHandle newUser) {
+ mCurrentUser = newUser;
+
+ // Role based shortcuts may resolve to different apps for different users
+ // so clear the cache.
+ mRoleIntents.clear();
+ mComponentIntents.clear();
+ }
+
/**
* Gets the shortcut intent for a given keycode+modifier. Make sure you
* strip whatever modifier is used for invoking shortcuts (for example,
@@ -147,9 +161,11 @@
* to invoke the shortcut.
* @return The intent that matches the shortcut, or null if not found.
*/
+ @Nullable
private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
// If a modifier key other than shift is also pressed, skip it.
- final boolean isShiftOn = KeyEvent.metaStateHasModifiers(metaState, KeyEvent.META_SHIFT_ON);
+ final boolean isShiftOn = KeyEvent.metaStateHasModifiers(
+ metaState, KeyEvent.META_SHIFT_ON);
if (!isShiftOn && !KeyEvent.metaStateHasNoModifiers(metaState)) {
return null;
}
@@ -161,37 +177,54 @@
// First try the exact keycode (with modifiers).
int shortcutChar = kcm.get(keyCode, metaState);
- if (shortcutChar != 0) {
+ if (shortcutChar == 0) {
+ return null;
+ }
+ shortcutIntent = shortcutMap.get(shortcutChar);
+
+ if (shortcutIntent == null) {
+ // Next try the primary character on that key.
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar == 0) {
+ return null;
+ }
shortcutIntent = shortcutMap.get(shortcutChar);
}
- // Next try the primary character on that key.
if (shortcutIntent == null) {
- shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
- if (shortcutChar != 0) {
- shortcutIntent = shortcutMap.get(shortcutChar);
-
- if (shortcutIntent == null) {
- // Check for role based shortcut
- String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
- : mRoleShortcuts.get(shortcutChar);
- if (role != null) {
- shortcutIntent = getRoleLaunchIntent(role);
- }
- }
+ // Next check for role based shortcut with primary character.
+ String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
+ : mRoleShortcuts.get(shortcutChar);
+ if (role != null) {
+ shortcutIntent = getRoleLaunchIntent(role);
}
}
+ if (modifierShortcutManagerMultiuser()) {
+ if (shortcutIntent == null) {
+ // Next check component based shortcuts with primary character.
+ ComponentName component = isShiftOn
+ ? mShiftComponentShortcuts.get(shortcutChar)
+ : mComponentShortcuts.get(shortcutChar);
+ if (component != null) {
+ shortcutIntent = resolveComponentNameIntent(component);
+ }
+ }
+ }
return shortcutIntent;
}
private Intent getRoleLaunchIntent(String role) {
Intent intent = mRoleIntents.get(role);
if (intent == null) {
- if (mRoleManager.isRoleAvailable(role)) {
- String rolePackage = mRoleManager.getDefaultApplication(role);
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ RoleManager rm = context.getSystemService(RoleManager.class);
+ PackageManager pm = context.getPackageManager();
+ if (rm.isRoleAvailable(role)) {
+ String rolePackage = rm.getDefaultApplication(role);
if (rolePackage != null) {
- intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
+ intent = pm.getLaunchIntentForPackage(rolePackage);
if (intent != null) {
intent.putExtra(EXTRA_ROLE, role);
mRoleIntents.put(role, intent);
@@ -249,7 +282,17 @@
+ " className=" + className + " shortcutChar=" + shortcutChar);
continue;
}
- intent = resolveComponentNameIntent(packageName, className);
+ if (modifierShortcutManagerMultiuser()) {
+ ComponentName componentName = new ComponentName(packageName, className);
+ if (isShiftShortcut) {
+ mShiftComponentShortcuts.put(shortcutChar, componentName);
+ } else {
+ mComponentShortcuts.put(shortcutChar, componentName);
+ }
+ continue;
+ } else {
+ intent = resolveComponentNameIntent(packageName, className);
+ }
} else if (categoryName != null) {
if (roleName != null) {
Log.w(TAG, "Cannot specify role bookmark when category is present for"
@@ -288,19 +331,37 @@
}
@Nullable
+ private Intent resolveComponentNameIntent(ComponentName componentName) {
+ Intent intent = mComponentIntents.get(componentName);
+ if (intent == null) {
+ intent = resolveComponentNameIntent(
+ componentName.getPackageName(), componentName.getClassName());
+ if (intent != null) {
+ mComponentIntents.put(componentName, intent);
+ }
+ }
+ return intent;
+ }
+
+ @Nullable
private Intent resolveComponentNameIntent(String packageName, String className) {
- int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ PackageManager pm = context.getPackageManager();
+ int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ if (!modifierShortcutManagerMultiuser()) {
+ flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+ }
ComponentName componentName = new ComponentName(packageName, className);
try {
- mPackageManager.getActivityInfo(componentName, flags);
+ pm.getActivityInfo(componentName, flags);
} catch (PackageManager.NameNotFoundException e) {
- String[] packages = mPackageManager.canonicalToCurrentPackageNames(
+ String[] packages = pm.canonicalToCurrentPackageNames(
new String[] { packageName });
componentName = new ComponentName(packages[0], className);
try {
- mPackageManager.getActivityInfo(componentName, flags);
+ pm.getActivityInfo(componentName, flags);
} catch (PackageManager.NameNotFoundException e1) {
Log.w(TAG, "Unable to add bookmark: " + packageName
+ "/" + className + " not found.");
@@ -399,7 +460,11 @@
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ if (modifierShortcutManagerMultiuser()) {
+ mContext.startActivityAsUser(intent, mCurrentUser);
+ } else {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
} catch (ActivityNotFoundException ex) {
Slog.w(TAG, "Dropping application launch key because "
+ "the activity to which it is registered was not found: "
@@ -417,7 +482,11 @@
if (shortcutIntent != null) {
shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+ if (modifierShortcutManagerMultiuser()) {
+ mContext.startActivityAsUser(shortcutIntent, mCurrentUser);
+ } else {
+ mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+ }
} catch (ActivityNotFoundException ex) {
Slog.w(TAG, "Dropping shortcut key combination because "
+ "the activity to which it is registered was not found: "
@@ -526,6 +595,30 @@
}
}
+ if (modifierShortcutManagerMultiuser()) {
+ for (int i = 0; i < mComponentShortcuts.size(); i++) {
+ ComponentName component = mComponentShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mComponentShortcuts.keyAt(i)),
+ resolveComponentNameIntent(component),
+ false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+ ComponentName component = mShiftComponentShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftComponentShortcuts.keyAt(i)),
+ resolveComponentNameIntent(component),
+ true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ }
+
return new KeyboardShortcutGroup(
mContext.getString(R.string.keyboard_shortcut_group_applications),
shortcuts);
@@ -548,23 +641,26 @@
CharSequence label;
Icon icon;
+ Context context = modifierShortcutManagerMultiuser()
+ ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+ PackageManager pm = context.getPackageManager();
ActivityInfo resolvedActivity = intent.resolveActivityInfo(
- mPackageManager, PackageManager.MATCH_DEFAULT_ONLY);
+ pm, PackageManager.MATCH_DEFAULT_ONLY);
if (resolvedActivity == null) {
return null;
}
boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals(
resolvedActivity.name);
if (isResolver) {
- label = getIntentCategoryLabel(mContext,
+ label = getIntentCategoryLabel(context,
intent.getSelector().getCategories().iterator().next());
if (label == null) {
return null;
}
- icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon);
+ icon = Icon.createWithResource(context, R.drawable.sym_def_app_icon);
} else {
- label = resolvedActivity.loadLabel(mPackageManager);
+ label = resolvedActivity.loadLabel(pm);
icon = Icon.createWithResource(
resolvedActivity.packageName, resolvedActivity.getIconResource());
}
@@ -609,5 +705,4 @@
}
return context.getString(resid);
};
-
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index df97313..7534bfe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -76,6 +76,7 @@
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
@@ -2281,7 +2282,8 @@
mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
- mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+ mModifierShortcutManager = new ModifierShortcutManager(
+ mContext, mHandler, UserHandle.of(mCurrentUserId));
mUiMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -6506,6 +6508,9 @@
if (statusBar != null) {
statusBar.setCurrentUser(newUserId);
}
+ if (modifierShortcutManagerMultiuser()) {
+ mModifierShortcutManager.setCurrentUser(UserHandle.of(newUserId));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 5fab13b..689b495 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -44,9 +44,9 @@
*/
abstract class Vibration {
private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+ "HH:mm:ss.SSS");
private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+ "MM-dd HH:mm:ss.SSS");
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -244,12 +244,10 @@
@Override
public String toString() {
- return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
- Instant.ofEpochMilli(mCreateTime))
- + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
- Instant.ofEpochMilli(mStartTime))
- + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
- Instant.ofEpochMilli(mEndTime)))
+ return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+ + ", startTime: " + formatTime(mStartTime, /*includeDate=*/ true)
+ + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+ /*includeDate=*/ true))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", playedEffect: " + mPlayedEffect
@@ -273,14 +271,12 @@
boolean isExternalVibration = mPlayedEffect == null;
String timingsStr = String.format(Locale.ROOT,
"%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
- DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
+ formatTime(mCreateTime, /*includeDate=*/ true),
isExternalVibration ? "external" : "effect",
mStatus.name().toLowerCase(Locale.ROOT),
mDurationMs,
- mStartTime == 0 ? ""
- : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
- mEndTime == 0 ? ""
- : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
+ mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false),
+ mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false));
String paramStr = String.format(Locale.ROOT,
" | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
@@ -315,12 +311,10 @@
pw.increaseIndent();
pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
pw.println("durationMs = " + mDurationMs);
- pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
- Instant.ofEpochMilli(mCreateTime)));
- pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
- Instant.ofEpochMilli(mStartTime)));
+ pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true));
+ pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true));
pw.println("endTime = " + (mEndTime == 0 ? null
- : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
+ : formatTime(mEndTime, /*includeDate=*/ true)));
pw.println("playedEffect = " + mPlayedEffect);
pw.println("originalEffect = " + mOriginalEffect);
pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
@@ -458,5 +452,12 @@
proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
proto.end(token);
}
+
+ private String formatTime(long timeInMillis, boolean includeDate) {
+ return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
+ // Ensure timezone is retrieved at formatting time
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(timeInMillis));
+ }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index f82ff67..4da6585 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -68,7 +68,7 @@
private static final int NO_SCALE = -1;
private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+ "MM-dd HH:mm:ss.SSS");
private final VibrationParamsRecords mVibrationParamsRecords;
private final VibratorControllerHolder mVibratorControllerHolder;
@@ -591,7 +591,8 @@
public void dump(IndentingPrintWriter pw) {
String line = String.format(Locale.ROOT,
"%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
- DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
+ DEBUG_DATE_TIME_FORMATTER.withZone(ZoneId.systemDefault()).format(
+ Instant.ofEpochMilli(mCreateTime)),
mOperation.name().toLowerCase(Locale.ROOT),
(mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
Long.toBinaryString(mTypesMask), createVibrationUsagesString());
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 7ce9de4..7c31177 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -16,39 +16,185 @@
package com.android.server.wm;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
import android.annotation.NonNull;
import android.graphics.Rect;
+import android.util.Log;
+import android.view.Surface;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
+import com.android.internal.protolog.ProtoLog;
-/**
- * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
- * black layers of varying opacity at various Z-levels which create the effect of a Dim.
- */
-public abstract class Dimmer {
-
- static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
+class Dimmer {
/**
* The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
* host, some controller of it, or one of the hosts children.
*/
- protected final WindowContainer mHost;
+ private final WindowContainer<?> mHost;
- protected Dimmer(WindowContainer host) {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ DimState mDimState;
+ final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
+
+ /**
+ * Controls the dim behaviour
+ */
+ protected class DimState {
+ /** Related objects */
+ SurfaceControl mDimSurface;
+ final WindowContainer<?> mHostContainer;
+ // The last container to request to dim
+ private WindowContainer<?> mLastRequestedDimContainer;
+ /** Animation */
+ private final DimmerAnimationHelper mAnimationHelper;
+ boolean mSkipAnimation = false;
+ // Determines whether the dim layer should animate before destroying.
+ boolean mAnimateExit = true;
+ /** Surface visibility and bounds */
+ private boolean mIsVisible = false;
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ DimState() {
+ mHostContainer = mHost;
+ mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
+ try {
+ mDimSurface = makeDimLayer();
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ void ensureVisible(@NonNull SurfaceControl.Transaction t) {
+ if (!mIsVisible) {
+ t.show(mDimSurface);
+ t.setAlpha(mDimSurface, 0f);
+ mIsVisible = true;
+ }
+ }
+
+ void adjustSurfaceLayout(@NonNull SurfaceControl.Transaction t) {
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
+ t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
+ }
+
+ /**
+ * Set the parameters to prepare the dim to change its appearance
+ */
+ void prepareLookChange(float alpha, int blurRadius) {
+ mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
+ }
+
+ /**
+ * Prepare the dim for the exit animation
+ */
+ void exit(@NonNull SurfaceControl.Transaction t) {
+ if (!mAnimateExit) {
+ remove(t);
+ } else {
+ mAnimationHelper.setExitParameters();
+ setReady(t);
+ }
+ }
+
+ void remove(@NonNull SurfaceControl.Transaction t) {
+ mAnimationHelper.stopCurrentAnimation(mDimSurface);
+ if (mDimSurface.isValid()) {
+ t.remove(mDimSurface);
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Removing dim surface %s on transaction %s", this, t);
+ } else {
+ Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Dimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
+ }
+
+ /**
+ * Set the parameters to prepare the dim to be relative parented to the dimming container
+ */
+ void prepareReparent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
+ mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
+ }
+
+ /**
+ * Call when all the changes have been requested to have them applied
+ * @param t The transaction in which to apply the changes
+ */
+ void setReady(@NonNull SurfaceControl.Transaction t) {
+ mAnimationHelper.applyChanges(t, this);
+ }
+
+ /**
+ * Whether anyone is currently requesting the dim
+ */
+ boolean isDimming() {
+ return mLastRequestedDimContainer != null;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("DimLayer.makeDimLayer")
+ .build();
+ }
+ }
+
+ protected Dimmer(@NonNull WindowContainer<?> host) {
+ this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
+ }
+
+ @VisibleForTesting
+ Dimmer(@NonNull WindowContainer host,
+ @NonNull DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
mHost = host;
+ mAnimationAdapterFactory = animationFactory;
}
- // Constructs the correct type of dimmer
- static Dimmer create(WindowContainer host) {
- return DIMMER_REFACTOR ? new SmoothDimmer(host) : new LegacyDimmer(host);
+ public boolean hostIsTask() {
+ return mHost.asTask() != null;
}
- @NonNull
- WindowContainer<?> getHost() {
- return mHost;
+ /**
+ * Mark all dims as pending completion on the next call to {@link #updateDims}
+ *
+ * Called before iterating on mHost's children, first step of dimming.
+ * This is intended for us by the host container, to be called at the beginning of
+ * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+ * chain {@link WindowContainer#prepareSurfaces} down to its children to give them
+ * a chance to request dims to continue.
+ */
+ void resetDimStates() {
+ if (mDimState != null) {
+ mDimState.mLastRequestedDimContainer = null;
+ }
+ }
+
+ /**
+ * Set the aspect of the dim layer, and request to keep dimming.
+ * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
+ * child should call setAppearance again to request the Dim to continue.
+ * If multiple containers call this method, only the changes relative to the topmost will be
+ * applied.
+ * @param dimmingContainer Container requesting the dim
+ * @param alpha Dim amount
+ * @param blurRadius Blur amount
+ */
+ protected void adjustAppearance(@NonNull WindowContainer<?> dimmingContainer,
+ float alpha, int blurRadius) {
+ final DimState d = obtainDimState(dimmingContainer);
+ d.prepareLookChange(alpha, blurRadius);
}
/**
@@ -62,42 +208,15 @@
* the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to
* continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState
* without also adjusting the appearance.
- * @param container The container which to dim above. Should be a child of the host.
+ * @param dimmingContainer The container which to dim above. Should be a child of the host.
* @param relativeLayer The position of the dim wrt the container
*/
- protected abstract void adjustRelativeLayer(WindowContainer container, int relativeLayer);
-
- /**
- * Set the aspect of the dim layer, and request to keep dimming.
- * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the
- * child should call setAppearance again to request the Dim to continue.
- * If multiple containers call this method, only the changes relative to the topmost will be
- * applied.
- * @param container Container requesting the dim
- * @param alpha Dim amount
- * @param blurRadius Blur amount
- */
- protected abstract void adjustAppearance(
- WindowContainer container, float alpha, int blurRadius);
-
- /**
- * Mark all dims as pending completion on the next call to {@link #updateDims}
- *
- * Called before iterating on mHost's children, first step of dimming.
- * This is intended for us by the host container, to be called at the beginning of
- * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
- * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
- * a chance to request dims to continue.
- */
- abstract void resetDimStates();
-
- /** Returns non-null bounds if the dimmer is showing. */
- abstract Rect getDimBounds();
-
- abstract void dontAnimateExit();
-
- @VisibleForTesting
- abstract SurfaceControl getDimLayer();
+ public void adjustRelativeLayer(@NonNull WindowContainer<?> dimmingContainer,
+ int relativeLayer) {
+ if (mDimState != null) {
+ mDimState.prepareReparent(dimmingContainer, relativeLayer);
+ }
+ }
/**
* Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -106,5 +225,51 @@
* @param t A transaction in which to update the dims.
* @return true if any Dims were updated.
*/
- abstract boolean updateDims(SurfaceControl.Transaction t);
+ boolean updateDims(@NonNull SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+ if (!mDimState.isDimming()) {
+ // No one is dimming, fade out and remove the dim
+ mDimState.exit(t);
+ mDimState = null;
+ return false;
+ } else {
+ // Someone is dimming, show the requested changes
+ mDimState.adjustSurfaceLayout(t);
+ final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
+ if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+ && ws.mActivityRecord.mStartingData != null) {
+ // Skip enter animation while starting window is on top of its activity
+ mDimState.mSkipAnimation = true;
+ }
+ mDimState.setReady(t);
+ return true;
+ }
+ }
+
+ @NonNull
+ private DimState obtainDimState(@NonNull WindowContainer<?> container) {
+ if (mDimState == null) {
+ mDimState = new DimState();
+ }
+ mDimState.mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ /** Returns non-null bounds if the dimmer is showing. */
+ @VisibleForTesting
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimSurface : null;
+ }
+
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 22fa88f..df1549e 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -25,6 +25,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.NonNull;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
@@ -46,13 +47,13 @@
static class Change {
private float mAlpha = -1f;
private int mBlurRadius = -1;
- private WindowContainer mDimmingContainer = null;
+ private WindowContainer<?> mDimmingContainer = null;
private int mRelativeLayer = -1;
private static final float EPSILON = 0.0001f;
Change() {}
- Change(Change other) {
+ Change(@NonNull Change other) {
mAlpha = other.mAlpha;
mBlurRadius = other.mBlurRadius;
mDimmingContainer = other.mDimmingContainer;
@@ -60,15 +61,15 @@
}
// Same alpha and blur
- boolean hasSameVisualProperties(Change other) {
+ boolean hasSameVisualProperties(@NonNull Change other) {
return Math.abs(mAlpha - other.mAlpha) < EPSILON && mBlurRadius == other.mBlurRadius;
}
- boolean hasSameDimmingContainer(Change other) {
+ boolean hasSameDimmingContainer(@NonNull Change other) {
return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
}
- void inheritPropertiesFromAnimation(AnimationSpec anim) {
+ void inheritPropertiesFromAnimation(@NonNull AnimationSpec anim) {
mAlpha = anim.mCurrentAlpha;
mBlurRadius = anim.mCurrentBlur;
}
@@ -97,7 +98,7 @@
}
// Sets a requested change without applying it immediately
- void setRequestedRelativeParent(WindowContainer relativeParent, int relativeLayer) {
+ void setRequestedRelativeParent(@NonNull WindowContainer<?> relativeParent, int relativeLayer) {
mRequestedProperties.mDimmingContainer = relativeParent;
mRequestedProperties.mRelativeLayer = relativeLayer;
}
@@ -114,7 +115,7 @@
* {@link Change#setRequestedRelativeParent(WindowContainer, int)}, or
* {@link Change#setRequestedAppearance(float, int)}
*/
- void applyChanges(SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+ void applyChanges(@NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
if (mRequestedProperties.mDimmingContainer == null) {
Log.e(TAG, this + " does not have a dimming container. Have you forgotten to "
+ "call adjustRelativeLayer?");
@@ -160,7 +161,7 @@
}
private void startAnimation(
- SurfaceControl.Transaction t, SmoothDimmer.DimState dim) {
+ @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
mAlphaAnimationSpec = getRequestedAnimationSpec();
mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
@@ -186,7 +187,7 @@
return mAlphaAnimationSpec != null;
}
- void stopCurrentAnimation(SurfaceControl surface) {
+ void stopCurrentAnimation(@NonNull SurfaceControl surface) {
if (mLocalAnimationAdapter != null && isAnimating()) {
// Save the current animation progress and cancel the animation
mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
@@ -196,6 +197,7 @@
}
}
+ @NonNull
private AnimationSpec getRequestedAnimationSpec() {
final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
@@ -214,8 +216,8 @@
/**
* Change the relative parent of this dim layer
*/
- void relativeReparent(SurfaceControl dimLayer, SurfaceControl relativeParent,
- int relativePosition, SurfaceControl.Transaction t) {
+ void relativeReparent(@NonNull SurfaceControl dimLayer, @NonNull SurfaceControl relativeParent,
+ int relativePosition, @NonNull SurfaceControl.Transaction t) {
try {
t.setRelativeLayer(dimLayer, relativeParent, relativePosition);
} catch (NullPointerException e) {
@@ -223,7 +225,8 @@
}
}
- void setAlphaBlur(SurfaceControl sc, float alpha, int blur, SurfaceControl.Transaction t) {
+ void setAlphaBlur(@NonNull SurfaceControl sc, float alpha, int blur,
+ @NonNull SurfaceControl.Transaction t) {
try {
t.setAlpha(sc, alpha);
t.setBackgroundBlurRadius(sc, blur);
@@ -232,7 +235,7 @@
}
}
- private long getDimDuration(WindowContainer container) {
+ private long getDimDuration(@NonNull WindowContainer<?> container) {
// Use the same duration as the animation on the WindowContainer
AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
@@ -282,7 +285,8 @@
}
@Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ public void apply(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc,
+ long currentPlayTime) {
if (!mStarted) {
// The first frame would end up in the sync transaction, and since this could be
// applied after the animation transaction, we avoid putting visible changes here.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index def495f..75724eb 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -817,7 +817,7 @@
* DisplayArea that can be dimmed.
*/
static class Dimmable extends DisplayArea<DisplayArea> {
- private final Dimmer mDimmer = Dimmer.create(this);
+ private final Dimmer mDimmer = new Dimmer(this);
Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fa60368..1efb3ef 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -619,8 +619,7 @@
private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
private AsyncRotationController mAsyncRotationController;
- final FixedRotationTransitionListener mFixedRotationTransitionListener =
- new FixedRotationTransitionListener();
+ final FixedRotationTransitionListener mFixedRotationTransitionListener;
@VisibleForTesting
final DeviceStateController mDeviceStateController;
@@ -1163,6 +1162,7 @@
TAG_WM + "/displayId:" + mDisplayId, mDisplayId);
mHoldScreenWakeLock.setReferenceCounted(false);
+ mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
@@ -6927,8 +6927,8 @@
/** Whether {@link #mAnimatingRecents} is going to be the top activity. */
private boolean mRecentsWillBeTop;
- FixedRotationTransitionListener() {
- super(DisplayContent.this.mDisplayId);
+ FixedRotationTransitionListener(int displayId) {
+ super(displayId);
}
/**
@@ -7017,7 +7017,7 @@
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
// effect of resume-while-pausing.
- if (r == null || r == mAnimatingRecents || r.getDisplayId() != mDisplayId) {
+ if (r == null || r == mAnimatingRecents) {
return;
}
if (mAnimatingRecents != null && mRecentsWillBeTop) {
@@ -7180,8 +7180,8 @@
@Override
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
try {
- // TODO stats token
mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver setImeInputTargetRequestedVisibility", e);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 91c61b1..4f4daa1 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -89,6 +89,7 @@
@Override
void onPostLayout() {
+ boolean wasServerVisible = mServerVisible;
super.onPostLayout();
if (android.view.inputmethod.Flags.refactorInsetsController()) {
@@ -101,11 +102,32 @@
// again, so that the control with leash can be eventually dispatched
if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
mGivenInsetsReady = true;
+ ImeTracker.forLogging().onProgress(mStatsToken,
+ ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStateController.notifyControlChanged(mControlTarget);
+ } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+ && givenInsetsPending) {
+ // If the server visibility didn't change (still visible), and mGivenInsetsReady
+ // is set, we won't call into notifyControlChanged. Therefore, we can reset the
+ // statsToken, if available.
+ ImeTracker.forLogging().onCancelled(mStatsToken,
+ ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
+ mStatsToken = null;
}
}
}
+ @Nullable
+ ImeTracker.Token getAndClearStatsToken() {
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ ImeTracker.Token statsToken = mStatsToken;
+ mStatsToken = null;
+ return statsToken;
+ } else {
+ return null;
+ }
+ }
+
@Override
protected boolean isLeashReadyForDispatching() {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
@@ -142,6 +164,14 @@
}
control.setSkipAnimationOnce(startingData != null && startingData.hasImeSurface());
}
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ if (control != null && control.getLeash() != null) {
+ ImeTracker.Token statsToken = getAndClearStatsToken();
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
+ control.setImeStatsToken(statsToken);
+ }
+ }
return control;
}
@@ -168,7 +198,7 @@
if (android.view.inputmethod.Flags.refactorInsetsController()) {
if (!serverVisible && !mFrozen) {
mGivenInsetsReady = false;
- updateControlForTarget(mControlTarget, true /* force */);
+ updateControlForTarget(mControlTarget, true /* force */, null /* statsToken */);
}
}
}
@@ -214,22 +244,29 @@
}
@Override
- void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+ void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force,
+ @NonNull ImeTracker.Token statsToken) {
if (target != null && target.getWindow() != null) {
// ime control target could be a different window.
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
- super.updateControlForTarget(target, force);
+ // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+ // not the case)
+ super.updateControlForTarget(target, force, statsToken);
if (Flags.refactorInsetsController()) {
+ // TODO(b/353463205) investigate if we should fail the statsToken, or if it's only
+ // temporary null.
if (target != null) {
- invokeOnImeRequestedChangedListener(target.getWindow());
+ invokeOnImeRequestedChangedListener(target.getWindow(), statsToken);
}
}
}
+ // TODO(b/353463205) change statsToken to be NonNull, after the flag is permanently enabled
@Override
- protected boolean updateClientVisibility(InsetsControlTarget caller) {
+ protected boolean updateClientVisibility(InsetsControlTarget caller,
+ @Nullable ImeTracker.Token statsToken) {
InsetsControlTarget controlTarget = getControlTarget();
if (caller != controlTarget) {
if (Flags.refactorInsetsController()) {
@@ -240,6 +277,8 @@
// its new requested visibility for the IME
boolean imeVisible = caller.isRequestedVisible(WindowInsets.Type.ime());
if (controlTarget != null) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
} else {
// In case of a virtual display that cannot show the IME, the
@@ -249,17 +288,24 @@
controlTarget = mDisplayContent.getImeHostOrFallback(caller.getWindow());
if (controlTarget != caller) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
}
}
WindowState windowState = caller.getWindow();
- invokeOnImeRequestedChangedListener(windowState);
+ invokeOnImeRequestedChangedListener(windowState, statsToken);
+ } else {
+ // TODO(b/353463205) add ImeTracker?
}
}
return false;
}
- boolean changed = super.updateClientVisibility(caller);
+ boolean changed = super.updateClientVisibility(caller, statsToken);
if (!Flags.refactorInsetsController()) {
if (changed && caller.isRequestedVisible(mSource.getType())) {
reportImeDrawnForOrganizerIfNeeded(caller);
@@ -273,7 +319,11 @@
WindowState windowState = caller.getWindow() != null ? caller.getWindow()
: ((mDisplayContent.getImeInputTarget() != null)
? mDisplayContent.getImeInputTarget().getWindowState() : null);
- invokeOnImeRequestedChangedListener(windowState);
+ invokeOnImeRequestedChangedListener(windowState, statsToken);
+ } else {
+ // TODO(b/329229469) change phase and check cancelled / failed
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
}
}
return changed;
@@ -288,22 +338,34 @@
// know about the new requestedVisibleTypes for the IME.
if (imeControlTarget != null) {
imeControlTarget.setImeInputTargetRequestedVisibility(
- (targetWin.getRequestedVisibleTypes()
- & WindowInsets.Type.ime()) != 0);
+ (targetWin.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
}
}
}
}
- private void invokeOnImeRequestedChangedListener(WindowState windowState) {
+ // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
+ private void invokeOnImeRequestedChangedListener(WindowState windowState,
+ @Nullable ImeTracker.Token statsToken) {
final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener;
if (imeListener != null) {
if (windowState != null) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY);
mDisplayContent.mWmService.mH.post(() -> {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER);
imeListener.onImeRequestedChanged(windowState.mClient.asBinder(),
- windowState.isRequestedVisible(WindowInsets.Type.ime()));
+ windowState.isRequestedVisible(WindowInsets.Type.ime()), statsToken);
});
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY);
}
+ } else {
+ // TODO(b/353463205) We could combine the upper if's and remove the additional phase.
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_DISPATCH_IME_REQUESTED_CHANGED);
}
}
@@ -399,9 +461,9 @@
// This can later become ready, so we don't want to cancel the pending request here.
return;
}
+ // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+ // (DefaultImeVisibilityApplier)
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- // Clear token here so we don't report an error in abortShowImePostLayout().
- abortShowImePostLayout();
// The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
// if we have a leash
if (mControl != null && mControl.getLeash() != null
@@ -517,6 +579,33 @@
}
/**
+ * Sets the statsToken before the IMS was shown/hidden.
+ * @param visible {@code true} to make it visible, false to hide it.
+ * @param statsToken the token tracking the current IME request.
+ */
+ void receiveImeStatsToken(boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ return;
+ }
+
+ if (mStatsToken != null) {
+ // We have an ongoing show request will be cancelled by the newly received show
+ // request (cancelling the initial show) or hide request (aborting the initial show).
+ logIsScheduledAndReadyToShowIme(!visible /* aborted */);
+ }
+ if (visible) {
+ ImeTracker.forLogging().onCancelled(
+ mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+ mStatsToken = statsToken;
+ } else {
+ ImeTracker.forLogging().onFailed(
+ mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+ mStatsToken = null;
+ }
+ }
+
+ /**
* Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}.
*
* @param aborted whether the scheduled show IME request was aborted or cancelled.
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 6288a42..62bef74 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -47,6 +47,7 @@
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowManager;
+import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
@@ -433,8 +434,9 @@
return originalState;
}
- void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
- mStateController.onRequestedVisibleTypesChanged(caller);
+ void onRequestedVisibleTypesChanged(InsetsControlTarget caller,
+ @Nullable ImeTracker.Token statsToken) {
+ mStateController.onRequestedVisibleTypesChanged(caller, statsToken);
checkAbortTransient(caller);
updateBarControlTarget(mFocusedWin);
}
@@ -803,7 +805,8 @@
}
@Override
- public void updateRequestedVisibleTypes(int types) { }
+ public void updateRequestedVisibleTypes(int types, @Nullable ImeTracker.Token statsToken) {
+ }
@Override
public boolean hasAnimationCallbacks() {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 33dea54..f5c92f6 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -46,6 +46,7 @@
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
@@ -498,7 +499,8 @@
);
}
- void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+ void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force,
+ @Nullable ImeTracker.Token statsToken) {
if (mSeamlessRotating) {
// We are un-rotating the window against the display rotation. We don't want the target
// to control the window for now.
@@ -570,7 +572,8 @@
mSeamlessRotating = false;
}
- boolean updateClientVisibility(InsetsControlTarget caller) {
+ boolean updateClientVisibility(InsetsControlTarget caller,
+ @Nullable ImeTracker.Token statsToken) {
final boolean requestedVisible = caller.isRequestedVisible(mSource.getType());
if (caller != mControlTarget || requestedVisible == mClientVisible) {
return false;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index dcadb0f..9c2a8de 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -40,6 +40,7 @@
import android.view.InsetsState;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
import com.android.internal.protolog.ProtoLog;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -216,10 +217,14 @@
}
}
- void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
+ void onRequestedVisibleTypesChanged(InsetsControlTarget caller,
+ @Nullable ImeTracker.Token statsToken) {
boolean changed = false;
for (int i = mProviders.size() - 1; i >= 0; i--) {
- changed |= mProviders.valueAt(i).updateClientVisibility(caller);
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ final boolean isImeProvider = provider.getSource().getType() == WindowInsets.Type.ime();
+ changed |= provider.updateClientVisibility(caller,
+ isImeProvider ? statsToken : null);
}
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
if (changed) {
@@ -310,7 +315,9 @@
// aborted.
provider.updateFakeControlTarget(target);
} else {
- provider.updateControlForTarget(target, false /* force */);
+ // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+ provider.updateControlForTarget(target, false /* force */,
+ null /* TODO(b/329229469) check if needed here */);
// Get control target again in case the provider didn't accept the one we passed to it.
target = provider.getControlTarget();
@@ -394,7 +401,9 @@
// to the clients, so that the clients can change the current visibilities to the
// requested visibilities with animations.
for (int i = newControlTargets.size() - 1; i >= 0; i--) {
- onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
+ // TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
+ // IME provider. Check if we have to create a new request here
+ onRequestedVisibleTypesChanged(newControlTargets.valueAt(i), null /* statsToken */);
}
newControlTargets.clear();
if (!android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
deleted file mode 100644
index 3265e60..0000000
--- a/services/core/java/com/android/server/wm/LegacyDimmer.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-
-public class LegacyDimmer extends Dimmer {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
- // This is in milliseconds.
- private static final int DEFAULT_DIM_ANIM_DURATION = 200;
- DimState mDimState;
- private WindowContainer mLastRequestedDimContainer;
- private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
- private class DimAnimatable implements SurfaceAnimator.Animatable {
- private SurfaceControl mDimLayer;
-
- private DimAnimatable(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mHost.getSyncTransaction();
- }
-
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mHost.getPendingTransaction();
- }
-
- @Override
- public void commitPendingTransaction() {
- mHost.commitPendingTransaction();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- }
-
- @Override
- public SurfaceControl.Builder makeAnimationLeash() {
- return mHost.makeAnimationLeash();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public SurfaceControl getSurfaceControl() {
- return mDimLayer;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public int getSurfaceWidth() {
- // This will determine the size of the leash created. This should be the size of the
- // host and not the dim layer since the dim layer may get bigger during animation. If
- // that occurs, the leash size cannot change so we need to ensure the leash is big
- // enough that the dim layer can grow.
- // This works because the mHost will be a Task which has the display bounds.
- return mHost.getSurfaceWidth();
- }
-
- @Override
- public int getSurfaceHeight() {
- // See getSurfaceWidth() above for explanation.
- return mHost.getSurfaceHeight();
- }
-
- void removeSurface() {
- if (mDimLayer != null && mDimLayer.isValid()) {
- getSyncTransaction().remove(mDimLayer);
- }
- mDimLayer = null;
- }
- }
-
- @VisibleForTesting
- class DimState {
- /**
- * The layer where property changes should be invoked on.
- */
- SurfaceControl mDimLayer;
- boolean mDimming;
- boolean mIsVisible;
-
- // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
- final Rect mDimBounds = new Rect();
-
- /**
- * Determines whether the dim layer should animate before destroying.
- */
- boolean mAnimateExit = true;
-
- /**
- * Used for Dims not associated with a WindowContainer.
- * See {@link Dimmer#adjustRelativeLayer(WindowContainer, int)} for details on Dim
- * lifecycle.
- */
- boolean mDontReset;
- SurfaceAnimator mSurfaceAnimator;
-
- DimState(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- mDimming = true;
- final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
- mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
- if (!mDimming) {
- dimAnimatable.removeSurface();
- }
- }, mHost.mWmService);
- }
- }
-
- @VisibleForTesting
- interface SurfaceAnimatorStarter {
- void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
- }
-
- protected LegacyDimmer(WindowContainer host) {
- this(host, SurfaceAnimator::startAnimation);
- }
-
- LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
- super(host);
- mSurfaceAnimatorStarter = surfaceAnimatorStarter;
- }
-
- private DimState obtainDimState(WindowContainer container) {
- if (mDimState == null) {
- try {
- final SurfaceControl ctl = makeDimLayer();
- mDimState = new DimState(ctl);
- } catch (Surface.OutOfResourcesException e) {
- Log.w(TAG, "OutOfResourcesException creating dim surface");
- }
- }
-
- mLastRequestedDimContainer = container;
- return mDimState;
- }
-
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("Dimmer.makeDimLayer")
- .build();
- }
-
- @Override
- SurfaceControl getDimLayer() {
- return mDimState != null ? mDimState.mDimLayer : null;
- }
-
- @Override
- void resetDimStates() {
- if (mDimState == null) {
- return;
- }
- if (!mDimState.mDontReset) {
- mDimState.mDimming = false;
- }
- }
-
- @Override
- Rect getDimBounds() {
- return mDimState != null ? mDimState.mDimBounds : null;
- }
-
- @Override
- void dontAnimateExit() {
- if (mDimState != null) {
- mDimState.mAnimateExit = false;
- }
- }
-
- @Override
- protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
- final DimState d = obtainDimState(container);
- if (d == null) {
- return;
- }
-
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- SurfaceControl.Transaction t = mHost.getPendingTransaction();
- t.setAlpha(d.mDimLayer, alpha);
- t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
- d.mDimming = true;
- }
-
- @Override
- protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
- final DimState d = mDimState;
- if (d != null) {
- SurfaceControl.Transaction t = mHost.getPendingTransaction();
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- }
- }
-
- @Override
- boolean updateDims(SurfaceControl.Transaction t) {
- if (mDimState == null) {
- return false;
- }
-
- if (!mDimState.mDimming) {
- if (!mDimState.mAnimateExit) {
- if (mDimState.mDimLayer.isValid()) {
- t.remove(mDimState.mDimLayer);
- }
- } else {
- startDimExit(mLastRequestedDimContainer,
- mDimState.mSurfaceAnimator, t);
- }
- mDimState = null;
- return false;
- } else {
- final Rect bounds = mDimState.mDimBounds;
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
- t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
- if (!mDimState.mIsVisible) {
- mDimState.mIsVisible = true;
- t.show(mDimState.mDimLayer);
- // Skip enter animation while starting window is on top of its activity
- final WindowState ws = mLastRequestedDimContainer.asWindowState();
- if (ws == null || ws.mActivityRecord == null
- || ws.mActivityRecord.mStartingData == null) {
- startDimEnter(mLastRequestedDimContainer,
- mDimState.mSurfaceAnimator, t);
- }
- }
- return true;
- }
- }
-
- private long getDimDuration(WindowContainer container) {
- // Use the same duration as the animation on the WindowContainer
- AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
- final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
- return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
- : animationAdapter.getDurationHint();
- }
-
- private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
- }
-
- private void startDimExit(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
- }
-
- private void startAnim(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
- mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
- new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
- mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_DIMMER);
- }
-
- private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
- private final long mDuration;
- private final float mFromAlpha;
- private final float mToAlpha;
-
- AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
- mFromAlpha = fromAlpha;
- mToAlpha = toAlpha;
- mDuration = duration;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
- t.setAlpha(sc, alpha);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
- pw.print(" to="); pw.print(mToAlpha);
- pw.print(" duration="); pw.println(mDuration);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ALPHA);
- proto.write(FROM, mFromAlpha);
- proto.write(TO, mToAlpha);
- proto.write(DURATION_MS, mDuration);
- proto.end(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index cc95518..32ec020 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -37,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -78,6 +79,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowRelayoutResult;
+import android.view.inputmethod.ImeTracker;
import android.window.InputTransferToken;
import android.window.OnBackInvokedCallbackInfo;
@@ -710,13 +712,20 @@
}
@Override
- public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes) {
+ public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes,
+ @Nullable ImeTracker.Token imeStatsToken) {
synchronized (mService.mGlobalLock) {
final WindowState win = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (win != null) {
+ ImeTracker.forLogging().onProgress(imeStatsToken,
+ ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
win.setRequestedVisibleTypes(requestedVisibleTypes);
- win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win);
+ win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
+ imeStatsToken);
+ } else {
+ ImeTracker.forLogging().onFailed(imeStatsToken,
+ ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
}
}
}
@@ -985,4 +994,28 @@
}
}
}
+
+ @Override
+ public void notifyImeWindowVisibilityChangedFromClient(IWindow window, boolean visible,
+ @NonNull ImeTracker.Token statsToken) {
+ synchronized (mService.mGlobalLock) {
+ // TODO(b/353463205) check if we can use mService.getDefaultDisplayContentLocked()
+ // instead of window
+ final WindowState win = mService.windowForClientLocked(this, window,
+ false /* throwOnError */);
+ if (win != null) {
+ final InsetsStateController insetsStateController =
+ win.getDisplayContent().getInsetsStateController();
+ ProtoLog.d(WM_DEBUG_IME, "notifyImeWindowVisibilityChangedFromClient: %s",
+ insetsStateController.getImeSourceProvider());
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_NOTIFY_IME_VISIBILITY_CHANGED_FROM_CLIENT);
+ insetsStateController.getImeSourceProvider().receiveImeStatsToken(visible,
+ statsToken);
+ } else {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_WM_NOTIFY_IME_VISIBILITY_CHANGED_FROM_CLIENT);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
deleted file mode 100644
index 2b4d901..0000000
--- a/services/core/java/com/android/server/wm/SmoothDimmer.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-class SmoothDimmer extends Dimmer {
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
- DimState mDimState;
- final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
-
- /**
- * Controls the dim behaviour
- */
- @VisibleForTesting
- class DimState {
- /** Related objects */
- SurfaceControl mDimSurface;
- final WindowContainer mHostContainer;
- // The last container to request to dim
- private WindowContainer mLastRequestedDimContainer;
- /** Animation */
- private final DimmerAnimationHelper mAnimationHelper;
- boolean mSkipAnimation = false;
- // Determines whether the dim layer should animate before destroying.
- boolean mAnimateExit = true;
- /** Surface visibility and bounds */
- private boolean mIsVisible = false;
- // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
- final Rect mDimBounds = new Rect();
-
- DimState() {
- mHostContainer = mHost;
- mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
- try {
- mDimSurface = makeDimLayer();
- } catch (Surface.OutOfResourcesException e) {
- Log.w(TAG, "OutOfResourcesException creating dim surface");
- }
- }
-
- void ensureVisible(SurfaceControl.Transaction t) {
- if (!mIsVisible) {
- t.show(mDimSurface);
- t.setAlpha(mDimSurface, 0f);
- mIsVisible = true;
- }
- }
-
- void adjustSurfaceLayout(SurfaceControl.Transaction t) {
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top);
- t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height());
- }
-
- /**
- * Set the parameters to prepare the dim to change its appearance
- */
- void prepareLookChange(float alpha, int blurRadius) {
- mAnimationHelper.setRequestedAppearance(alpha, blurRadius);
- }
-
- /**
- * Prepare the dim for the exit animation
- */
- void exit(SurfaceControl.Transaction t) {
- if (!mAnimateExit) {
- remove(t);
- } else {
- mAnimationHelper.setExitParameters();
- setReady(t);
- }
- }
-
- void remove(SurfaceControl.Transaction t) {
- mAnimationHelper.stopCurrentAnimation(mDimSurface);
- if (mDimSurface.isValid()) {
- t.remove(mDimSurface);
- ProtoLog.d(WM_DEBUG_DIMMER,
- "Removing dim surface %s on transaction %s", this, t);
- } else {
- Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n");
- }
- }
-
- @Override
- public String toString() {
- return "SmoothDimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
- }
-
- /**
- * Set the parameters to prepare the dim to be relative parented to the dimming container
- */
- void prepareReparent(WindowContainer relativeParent, int relativeLayer) {
- mAnimationHelper.setRequestedRelativeParent(relativeParent, relativeLayer);
- }
-
- /**
- * Call when all the changes have been requested to have them applied
- * @param t The transaction in which to apply the changes
- */
- void setReady(SurfaceControl.Transaction t) {
- mAnimationHelper.applyChanges(t, this);
- }
-
- /**
- * Whether anyone is currently requesting the dim
- */
- boolean isDimming() {
- return mLastRequestedDimContainer != null;
- }
-
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("DimLayer.makeDimLayer")
- .build();
- }
- }
-
- protected SmoothDimmer(WindowContainer host) {
- this(host, new DimmerAnimationHelper.AnimationAdapterFactory());
- }
-
- @VisibleForTesting
- SmoothDimmer(WindowContainer host,
- DimmerAnimationHelper.AnimationAdapterFactory animationFactory) {
- super(host);
- mAnimationAdapterFactory = animationFactory;
- }
-
- @Override
- void resetDimStates() {
- if (mDimState != null) {
- mDimState.mLastRequestedDimContainer = null;
- }
- }
-
- @Override
- protected void adjustAppearance(WindowContainer container, float alpha, int blurRadius) {
- final DimState d = obtainDimState(container);
- d.prepareLookChange(alpha, blurRadius);
- }
-
- @Override
- protected void adjustRelativeLayer(WindowContainer container, int relativeLayer) {
- if (mDimState != null) {
- mDimState.prepareReparent(container, relativeLayer);
- }
- }
-
- @Override
- boolean updateDims(SurfaceControl.Transaction t) {
- if (mDimState == null) {
- return false;
- }
- if (!mDimState.isDimming()) {
- // No one is dimming, fade out and remove the dim
- mDimState.exit(t);
- mDimState = null;
- return false;
- } else {
- // Someone is dimming, show the requested changes
- mDimState.adjustSurfaceLayout(t);
- final WindowState ws = mDimState.mLastRequestedDimContainer.asWindowState();
- if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
- && ws.mActivityRecord.mStartingData != null) {
- // Skip enter animation while starting window is on top of its activity
- mDimState.mSkipAnimation = true;
- }
- mDimState.setReady(t);
- return true;
- }
- }
-
- private DimState obtainDimState(WindowContainer container) {
- if (mDimState == null) {
- mDimState = new DimState();
- }
- mDimState.mLastRequestedDimContainer = container;
- return mDimState;
- }
-
- @Override
- @VisibleForTesting
- SurfaceControl getDimLayer() {
- return mDimState != null ? mDimState.mDimSurface : null;
- }
-
- @Override
- Rect getDimBounds() {
- return mDimState != null ? mDimState.mDimBounds : null;
- }
-
- @Override
- void dontAnimateExit() {
- if (mDimState != null) {
- mDimState.mAnimateExit = false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8f83a7c..259ca83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3214,7 +3214,7 @@
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
+ if (!isRootTask() || isTranslucentAndVisible()
|| (Flags.getDimmerOnClosing() ? isTranslucentForTransition()
: isTranslucent(null))) {
return super.getDimmer();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 9b2c022..29e82f7 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,8 +216,7 @@
*/
int mMinHeight;
- Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
- ? new SmoothDimmer(this) : new LegacyDimmer(this);
+ Dimmer mDimmer = new Dimmer(this);
/** Apply the dim layer on the embedded TaskFragment. */
static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
@@ -2436,7 +2435,7 @@
inOutConfig.smallestScreenWidthDp = (int) (0.5f
+ Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
} else if (windowingMode == WINDOWING_MODE_MULTI_WINDOW && mIsEmbedded
- && insideParentBounds && !resolvedBounds.equals(parentBounds)) {
+ && !resolvedBounds.equals(parentBounds)) {
// For embedded TFs, the smallest width should be updated. Otherwise, inherit
// from the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 65bc9a2..cf5a1e6 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -467,7 +467,7 @@
if (dimmer == null) {
return false;
}
- if (dimmer.getHost().asTask() != null) {
+ if (dimmer.hostIsTask()) {
// Always allow to dim if the host only affects its task.
return true;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1df251c..67d7b37 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1350,7 +1350,8 @@
private static boolean shouldDispatchLegacyListener(
WindowManagerInternal.AppTransitionListener listener, int displayId) {
// INVALID_DISPLAY means that it is a global listener.
- return listener.mDisplayId == Display.INVALID_DISPLAY || listener.mDisplayId == displayId;
+ return listener.mTargetDisplayId == Display.INVALID_DISPLAY
+ || listener.mTargetDisplayId == displayId;
}
void dispatchLegacyAppTransitionPending(int displayId) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 132e1ee..8ae1cf0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -247,7 +247,7 @@
* The display this listener is interested in. If it is INVALID_DISPLAY, then which display
* should be notified depends on the dispatcher.
*/
- public final int mDisplayId;
+ public final int mTargetDisplayId;
/** Let transition controller decide which display should receive the callbacks. */
public AppTransitionListener() {
@@ -256,7 +256,7 @@
/** It will listen the transition on the given display. */
public AppTransitionListener(int displayId) {
- mDisplayId = displayId;
+ mTargetDisplayId = displayId;
}
/**
@@ -365,8 +365,10 @@
*
* @param windowToken The window token
* @param imeVisible {@code true} if the IME should be shown, {@code false} to hide
+ * @param statsToken the token tracking the current IME request.
*/
- void onImeRequestedChanged(IBinder windowToken, boolean imeVisible);
+ void onImeRequestedChanged(IBinder windowToken, boolean imeVisible,
+ @Nullable ImeTracker.Token statsToken);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f65eea0..a218068 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4717,8 +4717,10 @@
return;
}
dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
+ // TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
+ // IME provider. Check if we have to create a new request here
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
- dc.mRemoteInsetsControlTarget);
+ dc.mRemoteInsetsControlTarget, null /* statsToken */);
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a36cff6..2860532 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5196,7 +5196,7 @@
private void applyDims() {
if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
- && (Dimmer.DIMMER_REFACTOR ? mWinAnimator.getShown() : isVisibleNow())
+ && mWinAnimator.getShown()
&& !mHidden && mTransitionController.canApplyDim(getTask())) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
@@ -5277,17 +5277,12 @@
void prepareSurfaces() {
mIsDimming = false;
if (mHasSurface) {
- if (!Dimmer.DIMMER_REFACTOR) {
- applyDims();
- }
updateSurfacePositionNonOrganized();
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
- if (Dimmer.DIMMER_REFACTOR) {
- applyDims();
- }
+ applyDims();
}
super.prepareSurfaces();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7cb8ace..3747850 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13975,6 +13975,8 @@
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
+ USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 84d2b7f..8e3248e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -538,6 +538,9 @@
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_SIM_GLOBALLY,
POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index acdbbde..e6dac88 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -398,17 +398,16 @@
if (randomNum >= traceFrequency) {
return;
}
- final int traceDelay = 1000;
final int traceDuration = 5000;
final String traceTag = "camera";
- BackgroundThread.get().getThreadHandler().postDelayed(() -> {
+ BackgroundThread.get().getThreadHandler().post(() -> {
try {
mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
traceDuration);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
- }, traceDelay);
+ });
}
}, null);
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 5533ff9..50041d0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -39,6 +39,7 @@
import android.content.res.XmlResourceParser;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
@@ -77,6 +78,7 @@
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
+ doReturn(mContext).when(mContext).createContextAsUser(anyObject(), anyInt());
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
@@ -98,7 +100,8 @@
.canonicalToCurrentPackageNames(aryEq(new String[] { "com.test2" }));
- mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+ mModifierShortcutManager = new ModifierShortcutManager(
+ mContext, mHandler, UserHandle.SYSTEM);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 24fc7ee..421bbae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3354,7 +3354,8 @@
// app1 requests IME visible.
app1.setRequestedVisibleTypes(ime(), ime());
- mDisplayContent.getInsetsStateController().onRequestedVisibleTypesChanged(app1);
+ mDisplayContent.getInsetsStateController().onRequestedVisibleTypesChanged(app1,
+ null /* statsToken */);
// Verify app1's IME insets is visible and app2's IME insets frozen flag set.
assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
@@ -3425,7 +3426,7 @@
assertFalse(activity2.mImeInsetsFrozenUntilStartInput);
app1.setRequestedVisibleTypes(ime());
- controller.onRequestedVisibleTypesChanged(app1);
+ controller.onRequestedVisibleTypesChanged(app1, null /* statsToken */);
// Expect all activities in split-screen will get IME insets visible state
assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 9efbe35..08f1dff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -21,12 +21,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
@@ -34,14 +32,11 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.utils.MockAnimationAdapter;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -131,33 +126,19 @@
}
}
- private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
- @Override
- public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
- surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
- }
- }
-
private MockSurfaceBuildingContainer mHost;
private Dimmer mDimmer;
private SurfaceControl.Transaction mTransaction;
private TestWindowContainer mChild;
private static AnimationAdapter sTestAnimation;
- private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
@Before
public void setUp() throws Exception {
mHost = new MockSurfaceBuildingContainer(mWm);
mTransaction = spy(StubTransaction.class);
mChild = new TestWindowContainer(mWm);
- if (Dimmer.DIMMER_REFACTOR) {
- sTestAnimation = spy(new MockAnimationAdapter());
- mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
- } else {
- sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
- mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
- }
+ sTestAnimation = spy(new MockAnimationAdapter());
+ mDimmer = new Dimmer(mHost, new MockAnimationAdapterFactory());
}
@Test
@@ -177,8 +158,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
final float alpha = 0.7f;
final int blur = 50;
mHost.addChild(mChild, 0);
@@ -197,23 +177,7 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
- final float alpha = 0.7f;
- mHost.addChild(mChild, 0);
- mDimmer.adjustAppearance(mChild, alpha, 20);
- mDimmer.adjustRelativeLayer(mChild, -1);
- SurfaceControl dimLayer = mDimmer.getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -232,25 +196,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
- mHost.addChild(mChild, 0);
-
- final float alpha = 0.8f;
- mDimmer.adjustAppearance(mChild, alpha, 20);
- mDimmer.adjustRelativeLayer(mChild, -1);
- SurfaceControl dimLayer = mDimmer.getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction);
- verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
- any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
- anyBoolean(),
- eq(ANIMATION_TYPE_DIMMER));
- verify(mHost.getPendingTransaction()).remove(dimLayer);
- }
-
- @Test
public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
mHost.addChild(mChild, 0);
@@ -292,8 +237,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testRemoveDimImmediately_Smooth() {
+ public void testRemoveDimImmediately() {
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 2);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -311,48 +255,11 @@
verify(mTransaction).remove(dimLayer);
}
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testRemoveDimImmediately_Legacy() {
- mHost.addChild(mChild, 0);
- mDimmer.adjustAppearance(mChild, 1, 0);
- mDimmer.adjustRelativeLayer(mChild, -1);
- SurfaceControl dimLayer = mDimmer.getDimLayer();
- mDimmer.updateDims(mTransaction);
- verify(mTransaction, times(1)).show(dimLayer);
-
- reset(sSurfaceAnimatorStarter);
- mDimmer.dontAnimateExit();
- mDimmer.resetDimStates();
- mDimmer.updateDims(mTransaction);
- verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
- any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
- eq(ANIMATION_TYPE_DIMMER));
- verify(mTransaction).remove(dimLayer);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
- public void testDimmerWithBlurUpdatesTransaction_Legacy() {
- mHost.addChild(mChild, 0);
-
- final int blurRadius = 50;
- mDimmer.adjustAppearance(mChild, 1, blurRadius);
- mDimmer.adjustRelativeLayer(mChild, -1);
- SurfaceControl dimLayer = mDimmer.getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
- }
-
/**
* mChild is requesting the dim values to be set directly. In this case, dim won't play the
* standard animation, but directly apply mChild's requests to the dim surface
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
public void testContainerDimsOpeningAnimationByItself() {
mHost.addChild(mChild, 0);
@@ -384,7 +291,6 @@
* alpha is animated to 0. This corner case is needed to verify that the layer is removed anyway
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
public void testContainerDimsClosingAnimationByItself() {
mHost.addChild(mChild, 0);
@@ -413,7 +319,6 @@
* Check the handover of the dim between two windows and the consequent dim animation in between
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
public void testMultipleContainersDimmingConsecutively() {
TestWindowContainer first = mChild;
TestWindowContainer second = new TestWindowContainer(mWm);
@@ -442,7 +347,6 @@
* updateDims will be satisfied
*/
@Test
- @RequiresFlagsEnabled(Flags.FLAG_INTRODUCE_SMOOTHER_DIMMER)
public void testMultipleContainersDimmingAtTheSameTime() {
TestWindowContainer first = mChild;
TestWindowContainer second = new TestWindowContainer(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c77a4d6..caeb41c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -554,7 +554,7 @@
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final InsetsSourceProvider navBarProvider = mNavBarWindow.getControllableInsetProvider();
- navBarProvider.updateControlForTarget(mAppWindow, false);
+ navBarProvider.updateControlForTarget(mAppWindow, false, null /* statsToken */);
navBarProvider.getSource().setVisible(false);
displayPolicy.setCanSystemBarsBeShownByUser(false);
@@ -579,7 +579,7 @@
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final InsetsSourceProvider navBarProvider = mNavBarWindow.getControllableInsetProvider();
- navBarProvider.updateControlForTarget(win, false);
+ navBarProvider.updateControlForTarget(win, false, null /* statsToken */);
navBarProvider.getSource().setVisible(false);
displayPolicy.setCanSystemBarsBeShownByUser(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index eeec54f..b26c267 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -361,7 +361,7 @@
mAppWindow.setRequestedVisibleTypes(
navigationBars() | statusBars(), navigationBars() | statusBars());
- policy.onRequestedVisibleTypesChanged(mAppWindow);
+ policy.onRequestedVisibleTypesChanged(mAppWindow, null /* statsToken */);
waitUntilWindowAnimatorIdle();
controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 2a025cd..6190807 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -112,13 +112,13 @@
statusBar.getFrame().set(0, 0, 500, 100);
// We must not have control or control target before we have the insets source window.
- mProvider.updateControlForTarget(target, true /* force */);
+ mProvider.updateControlForTarget(target, true /* force */, null /* statsToken */);
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
// We can have the control or the control target after we have the insets source window.
mProvider.setWindowContainer(statusBar, null, null);
- mProvider.updateControlForTarget(target, false /* force */);
+ mProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
assertNotNull(mProvider.getControl(target));
assertNotNull(mProvider.getControlTarget());
@@ -127,25 +127,25 @@
mProvider.startSeamlessRotation();
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
- mProvider.updateControlForTarget(target, true /* force */);
+ mProvider.updateControlForTarget(target, true /* force */, null /* statsToken */);
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
// We can have the control and the control target after seamless rotation.
mProvider.finishSeamlessRotation();
- mProvider.updateControlForTarget(target, false /* force */);
+ mProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
assertNotNull(mProvider.getControl(target));
assertNotNull(mProvider.getControlTarget());
// We can clear the control and the control target.
- mProvider.updateControlForTarget(null, false /* force */);
+ mProvider.updateControlForTarget(null, false /* force */, null /* statsToken */);
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
// We must not have control or control target if the insets source window doesn't have a
// surface.
statusBar.setSurfaceControl(null);
- mProvider.updateControlForTarget(target, true /* force */);
+ mProvider.updateControlForTarget(target, true /* force */, null /* statsToken */);
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
}
@@ -173,7 +173,7 @@
// We must not have control or control target before we have the insets source window,
// so also no leash.
- mProvider.updateControlForTarget(target, true /* force */);
+ mProvider.updateControlForTarget(target, true /* force */, null /* statsToken */);
assertNull(mProvider.getControl(target));
assertNull(mProvider.getControlTarget());
assertNull(mProvider.getLeash(target));
@@ -181,7 +181,7 @@
// We can have the control or the control target after we have the insets source window,
// but no leash as this is not yet ready for dispatching.
mProvider.setWindowContainer(statusBar, null, null);
- mProvider.updateControlForTarget(target, false /* force */);
+ mProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
assertNotNull(mProvider.getControl(target));
assertNotNull(mProvider.getControlTarget());
assertEquals(mProvider.getControlTarget(), target);
@@ -265,9 +265,9 @@
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
- mProvider.updateControlForTarget(target, false /* force */);
+ mProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
target.setRequestedVisibleTypes(0, statusBars());
- mProvider.updateClientVisibility(target);
+ mProvider.updateClientVisibility(target, null /* statsToken */);
assertFalse(mSource.isVisible());
}
@@ -278,7 +278,7 @@
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
target.setRequestedVisibleTypes(0, statusBars());
- mProvider.updateClientVisibility(target);
+ mProvider.updateClientVisibility(target, null /* statsToken */);
assertTrue(mSource.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index c69faed..0dc56f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -201,7 +201,7 @@
.setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(base);
base.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(base);
+ getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
// Send our spy window (app) into the system so that we can detect the invocation.
final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -445,7 +445,7 @@
waitUntilHandlersIdle();
clearInvocations(mDisplayContent);
- imeSourceProvider.updateControlForTarget(app, false /* force */);
+ imeSourceProvider.updateControlForTarget(app, false /* force */, null /* statsToken */);
imeSourceProvider.setClientVisible(true);
verify(mDisplayContent).assignWindowLayers(anyBoolean());
waitUntilHandlersIdle();
@@ -497,7 +497,7 @@
mDisplayContent.updateImeInputAndControlTarget(app);
app.setRequestedVisibleTypes(ime(), ime());
- getController().onRequestedVisibleTypesChanged(app);
+ getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
@@ -544,7 +544,7 @@
imeInsetsProvider.setWindowContainer(mImeWindow, null, null);
imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame());
- imeInsetsProvider.updateControlForTarget(app1, false);
+ imeInsetsProvider.updateControlForTarget(app1, false, null /* statsToken */);
imeInsetsProvider.onPostLayout();
final InsetsSourceControl control1 = imeInsetsProvider.getControl(app1);
assertNotNull(control1);
@@ -553,7 +553,7 @@
// Simulate the IME control target updated from app1 to app2 when IME insets was invisible.
imeInsetsProvider.setServerVisible(false);
- imeInsetsProvider.updateControlForTarget(app2, false);
+ imeInsetsProvider.updateControlForTarget(app2, false, null /* statsToken */);
// Verify insetsHint of the new control is same as last IME source frame after the layout.
imeInsetsProvider.onPostLayout();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 47d34a6..3c247a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -1049,6 +1049,28 @@
assertFalse(taskFragment.shouldBeVisible(null));
}
+ @Test
+ public void testTaskFragmentSmallestScreenWidthDp() {
+ // Create an embedded TaskFragment in a Task.
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .build();
+ final Rect taskBounds = task.getBounds();
+
+ // Making the bounds of the embedded TaskFragment smaller than the parent Task.
+ taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right / 2,
+ taskBounds.bottom);
+
+ // The swdp should be calculated via the TF bounds when it is a multi-window TF.
+ final Configuration outConfig = new Configuration();
+ outConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ taskFragment.computeConfigResourceOverrides(outConfig, task.getConfiguration());
+ assertEquals(outConfig.smallestScreenWidthDp,
+ Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp));
+ }
+
private WindowState createAppWindow(ActivityRecord app, String name) {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e13376b..b46189c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -472,7 +472,7 @@
app.setRequestedVisibleTypes(0, statusBars());
mDisplayContent.getInsetsStateController()
.getOrCreateSourceProvider(statusBarId, statusBars())
- .updateClientVisibility(app);
+ .updateClientVisibility(app, null /* statsToken */);
waitUntilHandlersIdle();
assertFalse(statusBar.isVisible());
}